Search in sources :

Example 26 with HlsMediaPlaylist

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

the class HlsPlaylistParser method parseMediaPlaylist.

private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri) throws IOException {
    @HlsMediaPlaylist.PlaylistType int playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_UNKNOWN;
    long startOffsetUs = C.TIME_UNSET;
    int mediaSequence = 0;
    // Default version == 1.
    int version = 1;
    long targetDurationUs = C.TIME_UNSET;
    boolean hasEndTag = false;
    Segment initializationSegment = null;
    List<Segment> segments = new ArrayList<>();
    long segmentDurationUs = 0;
    boolean hasDiscontinuitySequence = false;
    int playlistDiscontinuitySequence = 0;
    int relativeDiscontinuitySequence = 0;
    long playlistStartTimeUs = 0;
    long segmentStartTimeUs = 0;
    long segmentByteRangeOffset = 0;
    long segmentByteRangeLength = C.LENGTH_UNSET;
    int segmentMediaSequence = 0;
    boolean isEncrypted = false;
    String encryptionKeyUri = null;
    String encryptionIV = null;
    String line;
    while (iterator.hasNext()) {
        line = iterator.next();
        if (line.startsWith(TAG_PLAYLIST_TYPE)) {
            String playlistTypeString = parseStringAttr(line, REGEX_PLAYLIST_TYPE);
            if ("VOD".equals(playlistTypeString)) {
                playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_VOD;
            } else if ("EVENT".equals(playlistTypeString)) {
                playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_EVENT;
            } else {
                throw new ParserException("Illegal playlist type: " + playlistTypeString);
            }
        } else if (line.startsWith(TAG_START)) {
            startOffsetUs = (long) (parseDoubleAttr(line, REGEX_TIME_OFFSET) * C.MICROS_PER_SECOND);
        } else if (line.startsWith(TAG_INIT_SEGMENT)) {
            String uri = parseStringAttr(line, REGEX_URI);
            String byteRange = parseOptionalStringAttr(line, REGEX_ATTR_BYTERANGE);
            if (byteRange != null) {
                String[] splitByteRange = byteRange.split("@");
                segmentByteRangeLength = Long.parseLong(splitByteRange[0]);
                if (splitByteRange.length > 1) {
                    segmentByteRangeOffset = Long.parseLong(splitByteRange[1]);
                }
            }
            initializationSegment = new Segment(uri, segmentByteRangeOffset, segmentByteRangeLength);
            segmentByteRangeOffset = 0;
            segmentByteRangeLength = C.LENGTH_UNSET;
        } else if (line.startsWith(TAG_TARGET_DURATION)) {
            targetDurationUs = parseIntAttr(line, REGEX_TARGET_DURATION) * C.MICROS_PER_SECOND;
        } else if (line.startsWith(TAG_MEDIA_SEQUENCE)) {
            mediaSequence = parseIntAttr(line, REGEX_MEDIA_SEQUENCE);
            segmentMediaSequence = mediaSequence;
        } else if (line.startsWith(TAG_VERSION)) {
            version = parseIntAttr(line, REGEX_VERSION);
        } else if (line.startsWith(TAG_MEDIA_DURATION)) {
            segmentDurationUs = (long) (parseDoubleAttr(line, REGEX_MEDIA_DURATION) * C.MICROS_PER_SECOND);
        } else if (line.startsWith(TAG_KEY)) {
            String method = parseStringAttr(line, REGEX_METHOD);
            isEncrypted = METHOD_AES128.equals(method);
            if (isEncrypted) {
                encryptionKeyUri = parseStringAttr(line, REGEX_URI);
                encryptionIV = parseOptionalStringAttr(line, REGEX_IV);
            } else {
                encryptionKeyUri = null;
                encryptionIV = null;
            }
        } else if (line.startsWith(TAG_BYTERANGE)) {
            String byteRange = parseStringAttr(line, REGEX_BYTERANGE);
            String[] splitByteRange = byteRange.split("@");
            segmentByteRangeLength = Long.parseLong(splitByteRange[0]);
            if (splitByteRange.length > 1) {
                segmentByteRangeOffset = Long.parseLong(splitByteRange[1]);
            }
        } else if (line.startsWith(TAG_DISCONTINUITY_SEQUENCE)) {
            hasDiscontinuitySequence = true;
            playlistDiscontinuitySequence = Integer.parseInt(line.substring(line.indexOf(':') + 1));
        } else if (line.equals(TAG_DISCONTINUITY)) {
            relativeDiscontinuitySequence++;
        } else if (line.startsWith(TAG_PROGRAM_DATE_TIME)) {
            if (playlistStartTimeUs == 0) {
                long programDatetimeUs = C.msToUs(Util.parseXsDateTime(line.substring(line.indexOf(':') + 1)));
                playlistStartTimeUs = programDatetimeUs - segmentStartTimeUs;
            }
        } else if (!line.startsWith("#")) {
            String segmentEncryptionIV;
            if (!isEncrypted) {
                segmentEncryptionIV = null;
            } else if (encryptionIV != null) {
                segmentEncryptionIV = encryptionIV;
            } else {
                segmentEncryptionIV = Integer.toHexString(segmentMediaSequence);
            }
            segmentMediaSequence++;
            if (segmentByteRangeLength == C.LENGTH_UNSET) {
                segmentByteRangeOffset = 0;
            }
            segments.add(new Segment(line, segmentDurationUs, relativeDiscontinuitySequence, segmentStartTimeUs, isEncrypted, encryptionKeyUri, segmentEncryptionIV, segmentByteRangeOffset, segmentByteRangeLength));
            segmentStartTimeUs += segmentDurationUs;
            segmentDurationUs = 0;
            if (segmentByteRangeLength != C.LENGTH_UNSET) {
                segmentByteRangeOffset += segmentByteRangeLength;
            }
            segmentByteRangeLength = C.LENGTH_UNSET;
        } else if (line.equals(TAG_ENDLIST)) {
            hasEndTag = true;
        }
    }
    return new HlsMediaPlaylist(playlistType, baseUri, startOffsetUs, playlistStartTimeUs, hasDiscontinuitySequence, playlistDiscontinuitySequence, mediaSequence, version, targetDurationUs, hasEndTag, playlistStartTimeUs != 0, initializationSegment, segments);
}
Also used : ParserException(com.google.android.exoplayer2.ParserException) ArrayList(java.util.ArrayList) Segment(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment)

Example 27 with HlsMediaPlaylist

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

the class HlsPlaylistTracker method getLoadedPlaylistDiscontinuitySequence.

private int getLoadedPlaylistDiscontinuitySequence(HlsMediaPlaylist oldPlaylist, HlsMediaPlaylist loadedPlaylist) {
    if (loadedPlaylist.hasDiscontinuitySequence) {
        return loadedPlaylist.discontinuitySequence;
    }
    // TODO: Improve cross-playlist discontinuity adjustment.
    int primaryUrlDiscontinuitySequence = primaryUrlSnapshot != null ? primaryUrlSnapshot.discontinuitySequence : 0;
    if (oldPlaylist == null) {
        return primaryUrlDiscontinuitySequence;
    }
    Segment firstOldOverlappingSegment = getFirstOldOverlappingSegment(oldPlaylist, loadedPlaylist);
    if (firstOldOverlappingSegment != null) {
        return oldPlaylist.discontinuitySequence + firstOldOverlappingSegment.relativeDiscontinuitySequence - loadedPlaylist.segments.get(0).relativeDiscontinuitySequence;
    }
    return primaryUrlDiscontinuitySequence;
}
Also used : Segment(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment)

Example 28 with HlsMediaPlaylist

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

the class HlsChunkSource method getNextMediaSequenceAndPartIndex.

// Private methods.
/**
 * Returns the media sequence number and part index to load next in the {@code mediaPlaylist}.
 *
 * @param previous The last (at least partially) loaded segment.
 * @param switchingTrack Whether the segment to load is not preceded by a segment in the same
 *     track.
 * @param mediaPlaylist The media playlist to which the segment to load belongs.
 * @param startOfPlaylistInPeriodUs The start of {@code mediaPlaylist} relative to the period
 *     start in microseconds.
 * @param loadPositionUs The current load position relative to the period start in microseconds.
 * @return The media sequence and part index to load.
 */
private Pair<Long, Integer> getNextMediaSequenceAndPartIndex(@Nullable HlsMediaChunk previous, boolean switchingTrack, HlsMediaPlaylist mediaPlaylist, long startOfPlaylistInPeriodUs, long loadPositionUs) {
    if (previous == null || switchingTrack) {
        long endOfPlaylistInPeriodUs = startOfPlaylistInPeriodUs + mediaPlaylist.durationUs;
        long targetPositionInPeriodUs = (previous == null || independentSegments) ? loadPositionUs : previous.startTimeUs;
        if (!mediaPlaylist.hasEndTag && targetPositionInPeriodUs >= endOfPlaylistInPeriodUs) {
            // If the playlist is too old to contain the chunk, we need to refresh it.
            return new Pair<>(mediaPlaylist.mediaSequence + mediaPlaylist.segments.size(), /* partIndex */
            C.INDEX_UNSET);
        }
        long targetPositionInPlaylistUs = targetPositionInPeriodUs - startOfPlaylistInPeriodUs;
        int segmentIndexInPlaylist = Util.binarySearchFloor(mediaPlaylist.segments, /* value= */
        targetPositionInPlaylistUs, /* inclusive= */
        true, /* stayInBounds= */
        !playlistTracker.isLive() || previous == null);
        long mediaSequence = segmentIndexInPlaylist + mediaPlaylist.mediaSequence;
        int partIndex = C.INDEX_UNSET;
        if (segmentIndexInPlaylist >= 0) {
            // In case we are inside the live window, we try to pick a part if available.
            Segment segment = mediaPlaylist.segments.get(segmentIndexInPlaylist);
            List<HlsMediaPlaylist.Part> parts = targetPositionInPlaylistUs < segment.relativeStartTimeUs + segment.durationUs ? segment.parts : mediaPlaylist.trailingParts;
            for (int i = 0; i < parts.size(); i++) {
                HlsMediaPlaylist.Part part = parts.get(i);
                if (targetPositionInPlaylistUs < part.relativeStartTimeUs + part.durationUs) {
                    if (part.isIndependent) {
                        partIndex = i;
                        // Increase media sequence by one if the part is a trailing part.
                        mediaSequence += parts == mediaPlaylist.trailingParts ? 1 : 0;
                    }
                    break;
                }
            }
        }
        return new Pair<>(mediaSequence, partIndex);
    }
    // If loading has not completed, we return the previous chunk again.
    return (previous.isLoadCompleted() ? new Pair<>(previous.partIndex == C.INDEX_UNSET ? previous.getNextChunkIndex() : previous.chunkIndex, previous.partIndex == C.INDEX_UNSET ? C.INDEX_UNSET : previous.partIndex + 1) : new Pair<>(previous.chunkIndex, previous.partIndex));
}
Also used : HlsMediaPlaylist(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist) Segment(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment) Pair(android.util.Pair)

Example 29 with HlsMediaPlaylist

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

the class HlsChunkSource method getSegmentBaseList.

// Package methods.
/**
 * Returns a list with all segment bases in the playlist starting from {@code mediaSequence} and
 * {@code partIndex} in the given playlist. The list may be empty if the starting point is not in
 * the playlist.
 */
@VisibleForTesting
static /* package */
List<HlsMediaPlaylist.SegmentBase> getSegmentBaseList(HlsMediaPlaylist playlist, long mediaSequence, int partIndex) {
    int firstSegmentIndexInPlaylist = (int) (mediaSequence - playlist.mediaSequence);
    if (firstSegmentIndexInPlaylist < 0 || playlist.segments.size() < firstSegmentIndexInPlaylist) {
        // The first media sequence is not in the playlist.
        return ImmutableList.of();
    }
    List<HlsMediaPlaylist.SegmentBase> segmentBases = new ArrayList<>();
    if (firstSegmentIndexInPlaylist < playlist.segments.size()) {
        if (partIndex != C.INDEX_UNSET) {
            // The iterator starts with a part that belongs to a segment.
            Segment firstSegment = playlist.segments.get(firstSegmentIndexInPlaylist);
            if (partIndex == 0) {
                // Use the full segment instead of the first part.
                segmentBases.add(firstSegment);
            } else if (partIndex < firstSegment.parts.size()) {
                // Add the parts from the first requested segment.
                segmentBases.addAll(firstSegment.parts.subList(partIndex, firstSegment.parts.size()));
            }
            firstSegmentIndexInPlaylist++;
        }
        partIndex = 0;
        // Add all remaining segments.
        segmentBases.addAll(playlist.segments.subList(firstSegmentIndexInPlaylist, playlist.segments.size()));
    }
    if (playlist.partTargetDurationUs != C.TIME_UNSET) {
        // That's a low latency playlist.
        partIndex = partIndex == C.INDEX_UNSET ? 0 : partIndex;
        if (partIndex < playlist.trailingParts.size()) {
            segmentBases.addAll(playlist.trailingParts.subList(partIndex, playlist.trailingParts.size()));
        }
    }
    return Collections.unmodifiableList(segmentBases);
}
Also used : ArrayList(java.util.ArrayList) Segment(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment) VisibleForTesting(androidx.annotation.VisibleForTesting)

Example 30 with HlsMediaPlaylist

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

the class HlsChunkSource method getAdjustedSeekPositionUs.

/**
 * Adjusts a seek position given the specified {@link SeekParameters}.
 *
 * @param positionUs The seek position in microseconds.
 * @param seekParameters Parameters that control how the seek is performed.
 * @return The adjusted seek position, in microseconds.
 */
public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
    int selectedIndex = trackSelection.getSelectedIndex();
    @Nullable HlsMediaPlaylist mediaPlaylist = selectedIndex < playlistUrls.length && selectedIndex != C.INDEX_UNSET ? playlistTracker.getPlaylistSnapshot(playlistUrls[trackSelection.getSelectedIndexInTrackGroup()], /* isForPlayback= */
    true) : null;
    if (mediaPlaylist == null || mediaPlaylist.segments.isEmpty() || !mediaPlaylist.hasIndependentSegments) {
        return positionUs;
    }
    // Segments start with sync samples (i.e., EXT-X-INDEPENDENT-SEGMENTS is set) and the playlist
    // is non-empty, so we can use segment start times as sync points. Note that in the rare case
    // that (a) an adaptive quality switch occurs between the adjustment and the seek being
    // performed, and (b) segment start times are not aligned across variants, it's possible that
    // the adjusted position may not be at a sync point when it was intended to be. However, this is
    // very much an edge case, and getting it wrong is worth it for getting the vast majority of
    // cases right whilst keeping the implementation relatively simple.
    long startOfPlaylistInPeriodUs = mediaPlaylist.startTimeUs - playlistTracker.getInitialStartTimeUs();
    long relativePositionUs = positionUs - startOfPlaylistInPeriodUs;
    int segmentIndex = Util.binarySearchFloor(mediaPlaylist.segments, relativePositionUs, /* inclusive= */
    true, /* stayInBounds= */
    true);
    long firstSyncUs = mediaPlaylist.segments.get(segmentIndex).relativeStartTimeUs;
    long secondSyncUs = firstSyncUs;
    if (segmentIndex != mediaPlaylist.segments.size() - 1) {
        secondSyncUs = mediaPlaylist.segments.get(segmentIndex + 1).relativeStartTimeUs;
    }
    return seekParameters.resolveSeekPositionUs(relativePositionUs, firstSyncUs, secondSyncUs) + startOfPlaylistInPeriodUs;
}
Also used : HlsMediaPlaylist(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist) Nullable(androidx.annotation.Nullable)

Aggregations

HlsMediaPlaylist (com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist)20 Uri (android.net.Uri)18 Segment (com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment)18 Test (org.junit.Test)18 InputStream (java.io.InputStream)11 ArrayList (java.util.ArrayList)11 Nullable (androidx.annotation.Nullable)9 DataSpec (com.google.android.exoplayer2.upstream.DataSpec)9 ByteArrayInputStream (java.io.ByteArrayInputStream)9 HlsPlaylistParser (com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser)4 IOException (java.io.IOException)3 ParserException (com.google.android.exoplayer2.ParserException)2 BehindLiveWindowException (com.google.android.exoplayer2.source.BehindLiveWindowException)2 BaseMediaChunkIterator (com.google.android.exoplayer2.source.chunk.BaseMediaChunkIterator)2 MediaChunkIterator (com.google.android.exoplayer2.source.chunk.MediaChunkIterator)2 FakeDataSource (com.google.android.exoplayer2.testutil.FakeDataSource)2 SystemClock (android.os.SystemClock)1 Pair (android.util.Pair)1 VisibleForTesting (androidx.annotation.VisibleForTesting)1 AndroidJUnit4 (androidx.test.ext.junit.runners.AndroidJUnit4)1