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);
}
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;
}
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));
}
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);
}
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;
}
Aggregations