public final class

HlsMediaPlaylist

extends HlsPlaylist

 java.lang.Object

androidx.media3.exoplayer.hls.playlist.HlsPlaylist

↳androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist

Gradle dependencies

compile group: 'androidx.media3', name: 'media3-exoplayer-hls', version: '1.5.0-alpha01'

  • groupId: androidx.media3
  • artifactId: media3-exoplayer-hls
  • version: 1.5.0-alpha01

Artifact androidx.media3:media3-exoplayer-hls:1.5.0-alpha01 it located at Google repository (https://maven.google.com/)

Overview

Represents an HLS media playlist.

Summary

Fields
public final intdiscontinuitySequence

The discontinuity sequence number of the first media segment in the playlist, as defined by #EXT-X-DISCONTINUITY-SEQUENCE.

public final longdurationUs

The total duration of the playlist in microseconds.

public final booleanhasDiscontinuitySequence

Whether the playlist contains the #EXT-X-DISCONTINUITY-SEQUENCE tag.

public final booleanhasEndTag

Whether the playlist contains the #EXT-X-ENDLIST tag.

public final booleanhasPositiveStartOffset

Whether the HlsMediaPlaylist.startOffsetUs was explicitly defined by #EXT-X-START as a positive value or zero.

public final booleanhasProgramDateTime

Whether the playlist contains a #EXT-X-PROGRAM-DATE-TIME tag.

public final longmediaSequence

The media sequence number of the first media segment in the playlist, as defined by #EXT-X-MEDIA-SEQUENCE.

public final longpartTargetDurationUs

The target duration for segment parts, as defined by #EXT-X-PART-INF, or C.TIME_UNSET if undefined.

public static final intPLAYLIST_TYPE_EVENT

public static final intPLAYLIST_TYPE_UNKNOWN

public static final intPLAYLIST_TYPE_VOD

public final intplaylistType

The type of the playlist.

public final booleanpreciseStart

Whether the start position should be precise, as defined by #EXT-X-START.

public final DrmInitDataprotectionSchemes

Contains the CDM protection schemes used by segments in this playlist.

public final java.util.Map<Uri, HlsMediaPlaylist.RenditionReport>renditionReports

The rendition reports of alternative rendition playlists.

public final java.util.List<HlsMediaPlaylist.Segment>segments

The list of segments in the playlist.

public final HlsMediaPlaylist.ServerControlserverControl

The attributes of the #EXT-X-SERVER-CONTROL header.

public final longstartOffsetUs

The start offset in microseconds from the beginning of the playlist, as defined by #EXT-X-START, or C.TIME_UNSET if undefined.

public final longstartTimeUs

If HlsMediaPlaylist.hasProgramDateTime is true, contains the datetime as microseconds since epoch.

public final longtargetDurationUs

The target duration in microseconds, as defined by #EXT-X-TARGETDURATION.

public final java.util.List<HlsMediaPlaylist.Part>trailingParts

The list of parts at the end of the playlist for which the segment is not in the playlist yet.

public final intversion

The compatibility version, as defined by #EXT-X-VERSION.

from HlsPlaylistbaseUri, hasIndependentSegments, tags
Constructors
publicHlsMediaPlaylist(int playlistType, java.lang.String baseUri, java.util.List<java.lang.String> tags, long startOffsetUs, boolean preciseStart, long startTimeUs, boolean hasDiscontinuitySequence, int discontinuitySequence, long mediaSequence, int version, long targetDurationUs, long partTargetDurationUs, boolean hasIndependentSegments, boolean hasEndTag, boolean hasProgramDateTime, DrmInitData protectionSchemes, java.util.List<HlsMediaPlaylist.Segment> segments, java.util.List<HlsMediaPlaylist.Part> trailingParts, HlsMediaPlaylist.ServerControl serverControl, java.util.Map<Uri, HlsMediaPlaylist.RenditionReport> renditionReports)

Constructs an instance.

Methods
public HlsMediaPlaylistcopy(java.util.List<StreamKey> streamKeys)

public HlsMediaPlaylistcopyWith(long startTimeUs, int discontinuitySequence)

Returns a playlist identical to this one except for the start time, the discontinuity sequence and hasDiscontinuitySequence values.

public HlsMediaPlaylistcopyWithEndTag()

Returns a playlist identical to this one except that an end tag is added.

public longgetEndTimeUs()

Returns the result of adding the duration of the playlist to its start time.

public booleanisNewerThan(HlsMediaPlaylist other)

Returns whether this playlist is newer than other.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final int PLAYLIST_TYPE_UNKNOWN

public static final int PLAYLIST_TYPE_VOD

public static final int PLAYLIST_TYPE_EVENT

public final int playlistType

The type of the playlist. See HlsMediaPlaylist.PlaylistType.

public final long startOffsetUs

The start offset in microseconds from the beginning of the playlist, as defined by #EXT-X-START, or C.TIME_UNSET if undefined. The value is guaranteed to be between 0 and HlsMediaPlaylist.durationUs, inclusive.

public final boolean hasPositiveStartOffset

Whether the HlsMediaPlaylist.startOffsetUs was explicitly defined by #EXT-X-START as a positive value or zero.

public final boolean preciseStart

Whether the start position should be precise, as defined by #EXT-X-START.

public final long startTimeUs

If HlsMediaPlaylist.hasProgramDateTime is true, contains the datetime as microseconds since epoch. Otherwise, contains the aggregated duration of removed segments up to this snapshot of the playlist.

public final boolean hasDiscontinuitySequence

Whether the playlist contains the #EXT-X-DISCONTINUITY-SEQUENCE tag.

public final int discontinuitySequence

The discontinuity sequence number of the first media segment in the playlist, as defined by #EXT-X-DISCONTINUITY-SEQUENCE.

public final long mediaSequence

The media sequence number of the first media segment in the playlist, as defined by #EXT-X-MEDIA-SEQUENCE.

public final int version

The compatibility version, as defined by #EXT-X-VERSION.

public final long targetDurationUs

The target duration in microseconds, as defined by #EXT-X-TARGETDURATION.

public final long partTargetDurationUs

The target duration for segment parts, as defined by #EXT-X-PART-INF, or C.TIME_UNSET if undefined.

public final boolean hasEndTag

Whether the playlist contains the #EXT-X-ENDLIST tag.

public final boolean hasProgramDateTime

Whether the playlist contains a #EXT-X-PROGRAM-DATE-TIME tag.

public final DrmInitData protectionSchemes

Contains the CDM protection schemes used by segments in this playlist. Does not contain any key acquisition data. Null if none of the segments in the playlist is CDM-encrypted.

public final java.util.List<HlsMediaPlaylist.Segment> segments

The list of segments in the playlist.

public final java.util.List<HlsMediaPlaylist.Part> trailingParts

The list of parts at the end of the playlist for which the segment is not in the playlist yet.

public final java.util.Map<Uri, HlsMediaPlaylist.RenditionReport> renditionReports

The rendition reports of alternative rendition playlists.

public final long durationUs

The total duration of the playlist in microseconds.

public final HlsMediaPlaylist.ServerControl serverControl

The attributes of the #EXT-X-SERVER-CONTROL header.

Constructors

public HlsMediaPlaylist(int playlistType, java.lang.String baseUri, java.util.List<java.lang.String> tags, long startOffsetUs, boolean preciseStart, long startTimeUs, boolean hasDiscontinuitySequence, int discontinuitySequence, long mediaSequence, int version, long targetDurationUs, long partTargetDurationUs, boolean hasIndependentSegments, boolean hasEndTag, boolean hasProgramDateTime, DrmInitData protectionSchemes, java.util.List<HlsMediaPlaylist.Segment> segments, java.util.List<HlsMediaPlaylist.Part> trailingParts, HlsMediaPlaylist.ServerControl serverControl, java.util.Map<Uri, HlsMediaPlaylist.RenditionReport> renditionReports)

Constructs an instance.

Parameters:

playlistType: See HlsMediaPlaylist.playlistType.
baseUri: See HlsPlaylist.baseUri.
tags: See HlsPlaylist.tags.
startOffsetUs: See HlsMediaPlaylist.startOffsetUs.
preciseStart: See HlsMediaPlaylist.preciseStart.
startTimeUs: See HlsMediaPlaylist.startTimeUs.
hasDiscontinuitySequence: See HlsMediaPlaylist.hasDiscontinuitySequence.
discontinuitySequence: See HlsMediaPlaylist.discontinuitySequence.
mediaSequence: See HlsMediaPlaylist.mediaSequence.
version: See HlsMediaPlaylist.version.
targetDurationUs: See HlsMediaPlaylist.targetDurationUs.
partTargetDurationUs: See HlsMediaPlaylist.partTargetDurationUs.
hasIndependentSegments: See HlsPlaylist.hasIndependentSegments.
hasEndTag: See HlsMediaPlaylist.hasEndTag.
hasProgramDateTime: See HlsMediaPlaylist.hasProgramDateTime.
protectionSchemes: See HlsMediaPlaylist.protectionSchemes.
segments: See HlsMediaPlaylist.segments.
trailingParts: See HlsMediaPlaylist.trailingParts.
serverControl: See HlsMediaPlaylist.serverControl
renditionReports: See HlsMediaPlaylist.renditionReports.

Methods

public HlsMediaPlaylist copy(java.util.List<StreamKey> streamKeys)

public boolean isNewerThan(HlsMediaPlaylist other)

Returns whether this playlist is newer than other.

Parameters:

other: The playlist to compare.

Returns:

Whether this playlist is newer than other.

public long getEndTimeUs()

Returns the result of adding the duration of the playlist to its start time.

public HlsMediaPlaylist copyWith(long startTimeUs, int discontinuitySequence)

Returns a playlist identical to this one except for the start time, the discontinuity sequence and hasDiscontinuitySequence values. The first two are set to the specified values, hasDiscontinuitySequence is set to true.

Parameters:

startTimeUs: The start time for the returned playlist.
discontinuitySequence: The discontinuity sequence for the returned playlist.

Returns:

An identical playlist including the provided discontinuity and timing information.

public HlsMediaPlaylist copyWithEndTag()

Returns a playlist identical to this one except that an end tag is added. If an end tag is already present then the playlist will return itself.

Source

/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.media3.exoplayer.hls.playlist;

import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.annotation.ElementType.TYPE_USE;

import android.net.Uri;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.DrmInitData;
import androidx.media3.common.StreamKey;
import androidx.media3.common.util.UnstableApi;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/** Represents an HLS media playlist. */
@UnstableApi
public final class HlsMediaPlaylist extends HlsPlaylist {

  /** Server control attributes. */
  public static final class ServerControl {

    /**
     * The skip boundary for delta updates in microseconds, or {@link C#TIME_UNSET} if delta updates
     * are not supported.
     */
    public final long skipUntilUs;

    /**
     * Whether the playlist can produce delta updates that skip older #EXT-X-DATERANGE tags in
     * addition to media segments.
     */
    public final boolean canSkipDateRanges;

    /**
     * The server-recommended live offset in microseconds, or {@link C#TIME_UNSET} if none defined.
     */
    public final long holdBackUs;

    /**
     * The server-recommended live offset in microseconds in low-latency mode, or {@link
     * C#TIME_UNSET} if none defined.
     */
    public final long partHoldBackUs;

    /** Whether the server supports blocking playlist reload. */
    public final boolean canBlockReload;

    /**
     * Creates a new instance.
     *
     * @param skipUntilUs See {@link #skipUntilUs}.
     * @param canSkipDateRanges See {@link #canSkipDateRanges}.
     * @param holdBackUs See {@link #holdBackUs}.
     * @param partHoldBackUs See {@link #partHoldBackUs}.
     * @param canBlockReload See {@link #canBlockReload}.
     */
    public ServerControl(
        long skipUntilUs,
        boolean canSkipDateRanges,
        long holdBackUs,
        long partHoldBackUs,
        boolean canBlockReload) {
      this.skipUntilUs = skipUntilUs;
      this.canSkipDateRanges = canSkipDateRanges;
      this.holdBackUs = holdBackUs;
      this.partHoldBackUs = partHoldBackUs;
      this.canBlockReload = canBlockReload;
    }
  }

  /** Media segment reference. */
  @SuppressWarnings("ComparableType")
  public static final class Segment extends SegmentBase {

    /** The human readable title of the segment. */
    public final String title;

    /** The parts belonging to this segment. */
    public final List<Part> parts;

    /**
     * Creates an instance to be used as init segment.
     *
     * @param uri See {@link #url}.
     * @param byteRangeOffset See {@link #byteRangeOffset}.
     * @param byteRangeLength See {@link #byteRangeLength}.
     * @param fullSegmentEncryptionKeyUri See {@link #fullSegmentEncryptionKeyUri}.
     * @param encryptionIV See {@link #encryptionIV}.
     */
    public Segment(
        String uri,
        long byteRangeOffset,
        long byteRangeLength,
        @Nullable String fullSegmentEncryptionKeyUri,
        @Nullable String encryptionIV) {
      this(
          uri,
          /* initializationSegment= */ null,
          /* title= */ "",
          /* durationUs= */ 0,
          /* relativeDiscontinuitySequence= */ -1,
          /* relativeStartTimeUs= */ C.TIME_UNSET,
          /* drmInitData= */ null,
          fullSegmentEncryptionKeyUri,
          encryptionIV,
          byteRangeOffset,
          byteRangeLength,
          /* hasGapTag= */ false,
          /* parts= */ ImmutableList.of());
    }

    /**
     * Creates an instance.
     *
     * @param url See {@link #url}.
     * @param initializationSegment See {@link #initializationSegment}.
     * @param title See {@link #title}.
     * @param durationUs See {@link #durationUs}.
     * @param relativeDiscontinuitySequence See {@link #relativeDiscontinuitySequence}.
     * @param relativeStartTimeUs See {@link #relativeStartTimeUs}.
     * @param drmInitData See {@link #drmInitData}.
     * @param fullSegmentEncryptionKeyUri See {@link #fullSegmentEncryptionKeyUri}.
     * @param encryptionIV See {@link #encryptionIV}.
     * @param byteRangeOffset See {@link #byteRangeOffset}.
     * @param byteRangeLength See {@link #byteRangeLength}.
     * @param hasGapTag See {@link #hasGapTag}.
     * @param parts See {@link #parts}.
     */
    public Segment(
        String url,
        @Nullable Segment initializationSegment,
        String title,
        long durationUs,
        int relativeDiscontinuitySequence,
        long relativeStartTimeUs,
        @Nullable DrmInitData drmInitData,
        @Nullable String fullSegmentEncryptionKeyUri,
        @Nullable String encryptionIV,
        long byteRangeOffset,
        long byteRangeLength,
        boolean hasGapTag,
        List<Part> parts) {
      super(
          url,
          initializationSegment,
          durationUs,
          relativeDiscontinuitySequence,
          relativeStartTimeUs,
          drmInitData,
          fullSegmentEncryptionKeyUri,
          encryptionIV,
          byteRangeOffset,
          byteRangeLength,
          hasGapTag);
      this.title = title;
      this.parts = ImmutableList.copyOf(parts);
    }

    public Segment copyWith(long relativeStartTimeUs, int relativeDiscontinuitySequence) {
      List<Part> updatedParts = new ArrayList<>();
      long relativePartStartTimeUs = relativeStartTimeUs;
      for (int i = 0; i < parts.size(); i++) {
        Part part = parts.get(i);
        updatedParts.add(part.copyWith(relativePartStartTimeUs, relativeDiscontinuitySequence));
        relativePartStartTimeUs += part.durationUs;
      }
      return new Segment(
          url,
          initializationSegment,
          title,
          durationUs,
          relativeDiscontinuitySequence,
          relativeStartTimeUs,
          drmInitData,
          fullSegmentEncryptionKeyUri,
          encryptionIV,
          byteRangeOffset,
          byteRangeLength,
          hasGapTag,
          updatedParts);
    }
  }

  /** A media part. */
  public static final class Part extends SegmentBase {

    /** Whether the part is independent. */
    public final boolean isIndependent;

    /** Whether the part is a preloading part. */
    public final boolean isPreload;

    /**
     * Creates an instance.
     *
     * @param url See {@link #url}.
     * @param initializationSegment See {@link #initializationSegment}.
     * @param durationUs See {@link #durationUs}.
     * @param relativeDiscontinuitySequence See {@link #relativeDiscontinuitySequence}.
     * @param relativeStartTimeUs See {@link #relativeStartTimeUs}.
     * @param drmInitData See {@link #drmInitData}.
     * @param fullSegmentEncryptionKeyUri See {@link #fullSegmentEncryptionKeyUri}.
     * @param encryptionIV See {@link #encryptionIV}.
     * @param byteRangeOffset See {@link #byteRangeOffset}.
     * @param byteRangeLength See {@link #byteRangeLength}.
     * @param hasGapTag See {@link #hasGapTag}.
     * @param isIndependent See {@link #isIndependent}.
     * @param isPreload See {@link #isPreload}.
     */
    public Part(
        String url,
        @Nullable Segment initializationSegment,
        long durationUs,
        int relativeDiscontinuitySequence,
        long relativeStartTimeUs,
        @Nullable DrmInitData drmInitData,
        @Nullable String fullSegmentEncryptionKeyUri,
        @Nullable String encryptionIV,
        long byteRangeOffset,
        long byteRangeLength,
        boolean hasGapTag,
        boolean isIndependent,
        boolean isPreload) {
      super(
          url,
          initializationSegment,
          durationUs,
          relativeDiscontinuitySequence,
          relativeStartTimeUs,
          drmInitData,
          fullSegmentEncryptionKeyUri,
          encryptionIV,
          byteRangeOffset,
          byteRangeLength,
          hasGapTag);
      this.isIndependent = isIndependent;
      this.isPreload = isPreload;
    }

    public Part copyWith(long relativeStartTimeUs, int relativeDiscontinuitySequence) {
      return new Part(
          url,
          initializationSegment,
          durationUs,
          relativeDiscontinuitySequence,
          relativeStartTimeUs,
          drmInitData,
          fullSegmentEncryptionKeyUri,
          encryptionIV,
          byteRangeOffset,
          byteRangeLength,
          hasGapTag,
          isIndependent,
          isPreload);
    }
  }

  /** The base for a {@link Segment} or a {@link Part} required for playback. */
  @SuppressWarnings("ComparableType")
  public static class SegmentBase implements Comparable<Long> {
    /** The url of the segment. */
    public final String url;

    /**
     * The media initialization section for this segment, as defined by #EXT-X-MAP. May be null if
     * the media playlist does not define a media initialization section for this segment. The same
     * instance is used for all segments that share an EXT-X-MAP tag.
     */
    @Nullable public final Segment initializationSegment;

    /** The duration of the segment in microseconds, as defined by #EXTINF or #EXT-X-PART. */
    public final long durationUs;

    /** The number of #EXT-X-DISCONTINUITY tags in the playlist before the segment. */
    public final int relativeDiscontinuitySequence;

    /** The start time of the segment in microseconds, relative to the start of the playlist. */
    public final long relativeStartTimeUs;

    /**
     * DRM initialization data for sample decryption, or null if the segment does not use CDM-DRM
     * protection.
     */
    @Nullable public final DrmInitData drmInitData;

    /**
     * The encryption identity key uri as defined by #EXT-X-KEY, or null if the segment does not use
     * full segment encryption with identity key.
     */
    @Nullable public final String fullSegmentEncryptionKeyUri;

    /**
     * The encryption initialization vector as defined by #EXT-X-KEY, or null if the segment is not
     * encrypted.
     */
    @Nullable public final String encryptionIV;

    /**
     * The segment's byte range offset, as defined by #EXT-X-BYTERANGE, #EXT-X-PART or
     * #EXT-X-PRELOAD-HINT.
     */
    public final long byteRangeOffset;

    /**
     * The segment's byte range length, as defined by #EXT-X-BYTERANGE, #EXT-X-PART or
     * #EXT-X-PRELOAD-HINT, or {@link C#LENGTH_UNSET} if no byte range is specified or the byte
     * range is open-ended.
     */
    public final long byteRangeLength;

    /** Whether the segment is marked as a gap. */
    public final boolean hasGapTag;

    private SegmentBase(
        String url,
        @Nullable Segment initializationSegment,
        long durationUs,
        int relativeDiscontinuitySequence,
        long relativeStartTimeUs,
        @Nullable DrmInitData drmInitData,
        @Nullable String fullSegmentEncryptionKeyUri,
        @Nullable String encryptionIV,
        long byteRangeOffset,
        long byteRangeLength,
        boolean hasGapTag) {
      this.url = url;
      this.initializationSegment = initializationSegment;
      this.durationUs = durationUs;
      this.relativeDiscontinuitySequence = relativeDiscontinuitySequence;
      this.relativeStartTimeUs = relativeStartTimeUs;
      this.drmInitData = drmInitData;
      this.fullSegmentEncryptionKeyUri = fullSegmentEncryptionKeyUri;
      this.encryptionIV = encryptionIV;
      this.byteRangeOffset = byteRangeOffset;
      this.byteRangeLength = byteRangeLength;
      this.hasGapTag = hasGapTag;
    }

    @Override
    public int compareTo(Long relativeStartTimeUs) {
      return this.relativeStartTimeUs > relativeStartTimeUs
          ? 1
          : (this.relativeStartTimeUs < relativeStartTimeUs ? -1 : 0);
    }
  }

  /**
   * A rendition report for an alternative rendition defined in another media playlist.
   *
   * <p>See RFC 8216, section 4.4.5.1.4.
   */
  public static final class RenditionReport {
    /** The URI of the media playlist of the reported rendition. */
    public final Uri playlistUri;

    /** The last media sequence that is in the playlist of the reported rendition. */
    public final long lastMediaSequence;

    /**
     * The last part index that is in the playlist of the reported rendition, or {@link
     * C#INDEX_UNSET} if the rendition does not contain partial segments.
     */
    public final int lastPartIndex;

    /**
     * Creates a new instance.
     *
     * @param playlistUri See {@link #playlistUri}.
     * @param lastMediaSequence See {@link #lastMediaSequence}.
     * @param lastPartIndex See {@link #lastPartIndex}.
     */
    public RenditionReport(Uri playlistUri, long lastMediaSequence, int lastPartIndex) {
      this.playlistUri = playlistUri;
      this.lastMediaSequence = lastMediaSequence;
      this.lastPartIndex = lastPartIndex;
    }
  }

  /**
   * Type of the playlist, as defined by #EXT-X-PLAYLIST-TYPE. One of {@link
   * #PLAYLIST_TYPE_UNKNOWN}, {@link #PLAYLIST_TYPE_VOD} or {@link #PLAYLIST_TYPE_EVENT}.
   */
  @Documented
  @Retention(RetentionPolicy.SOURCE)
  @Target(TYPE_USE)
  @IntDef({PLAYLIST_TYPE_UNKNOWN, PLAYLIST_TYPE_VOD, PLAYLIST_TYPE_EVENT})
  public @interface PlaylistType {}

  public static final int PLAYLIST_TYPE_UNKNOWN = 0;
  public static final int PLAYLIST_TYPE_VOD = 1;
  public static final int PLAYLIST_TYPE_EVENT = 2;

  /** The type of the playlist. See {@link PlaylistType}. */
  public final @PlaylistType int playlistType;

  /**
   * The start offset in microseconds from the beginning of the playlist, as defined by
   * #EXT-X-START, or {@link C#TIME_UNSET} if undefined. The value is guaranteed to be between 0 and
   * {@link #durationUs}, inclusive.
   */
  public final long startOffsetUs;

  /**
   * Whether the {@link #startOffsetUs} was explicitly defined by #EXT-X-START as a positive value
   * or zero.
   */
  public final boolean hasPositiveStartOffset;

  /** Whether the start position should be precise, as defined by #EXT-X-START. */
  public final boolean preciseStart;

  /**
   * If {@link #hasProgramDateTime} is true, contains the datetime as microseconds since epoch.
   * Otherwise, contains the aggregated duration of removed segments up to this snapshot of the
   * playlist.
   */
  public final long startTimeUs;

  /** Whether the playlist contains the #EXT-X-DISCONTINUITY-SEQUENCE tag. */
  public final boolean hasDiscontinuitySequence;

  /**
   * The discontinuity sequence number of the first media segment in the playlist, as defined by
   * #EXT-X-DISCONTINUITY-SEQUENCE.
   */
  public final int discontinuitySequence;

  /**
   * The media sequence number of the first media segment in the playlist, as defined by
   * #EXT-X-MEDIA-SEQUENCE.
   */
  public final long mediaSequence;

  /** The compatibility version, as defined by #EXT-X-VERSION. */
  public final int version;

  /** The target duration in microseconds, as defined by #EXT-X-TARGETDURATION. */
  public final long targetDurationUs;

  /**
   * The target duration for segment parts, as defined by #EXT-X-PART-INF, or {@link C#TIME_UNSET}
   * if undefined.
   */
  public final long partTargetDurationUs;

  /** Whether the playlist contains the #EXT-X-ENDLIST tag. */
  public final boolean hasEndTag;

  /** Whether the playlist contains a #EXT-X-PROGRAM-DATE-TIME tag. */
  public final boolean hasProgramDateTime;

  /**
   * Contains the CDM protection schemes used by segments in this playlist. Does not contain any key
   * acquisition data. Null if none of the segments in the playlist is CDM-encrypted.
   */
  @Nullable public final DrmInitData protectionSchemes;

  /** The list of segments in the playlist. */
  public final List<Segment> segments;

  /**
   * The list of parts at the end of the playlist for which the segment is not in the playlist yet.
   */
  public final List<Part> trailingParts;

  /** The rendition reports of alternative rendition playlists. */
  public final Map<Uri, RenditionReport> renditionReports;

  /** The total duration of the playlist in microseconds. */
  public final long durationUs;

  /** The attributes of the #EXT-X-SERVER-CONTROL header. */
  public final ServerControl serverControl;

  /**
   * Constructs an instance.
   *
   * @param playlistType See {@link #playlistType}.
   * @param baseUri See {@link #baseUri}.
   * @param tags See {@link #tags}.
   * @param startOffsetUs See {@link #startOffsetUs}.
   * @param preciseStart See {@link #preciseStart}.
   * @param startTimeUs See {@link #startTimeUs}.
   * @param hasDiscontinuitySequence See {@link #hasDiscontinuitySequence}.
   * @param discontinuitySequence See {@link #discontinuitySequence}.
   * @param mediaSequence See {@link #mediaSequence}.
   * @param version See {@link #version}.
   * @param targetDurationUs See {@link #targetDurationUs}.
   * @param partTargetDurationUs See {@link #partTargetDurationUs}.
   * @param hasIndependentSegments See {@link #hasIndependentSegments}.
   * @param hasEndTag See {@link #hasEndTag}.
   * @param hasProgramDateTime See {@link #hasProgramDateTime}.
   * @param protectionSchemes See {@link #protectionSchemes}.
   * @param segments See {@link #segments}.
   * @param trailingParts See {@link #trailingParts}.
   * @param serverControl See {@link #serverControl}
   * @param renditionReports See {@link #renditionReports}.
   */
  public HlsMediaPlaylist(
      @PlaylistType int playlistType,
      String baseUri,
      List<String> tags,
      long startOffsetUs,
      boolean preciseStart,
      long startTimeUs,
      boolean hasDiscontinuitySequence,
      int discontinuitySequence,
      long mediaSequence,
      int version,
      long targetDurationUs,
      long partTargetDurationUs,
      boolean hasIndependentSegments,
      boolean hasEndTag,
      boolean hasProgramDateTime,
      @Nullable DrmInitData protectionSchemes,
      List<Segment> segments,
      List<Part> trailingParts,
      ServerControl serverControl,
      Map<Uri, RenditionReport> renditionReports) {
    super(baseUri, tags, hasIndependentSegments);
    this.playlistType = playlistType;
    this.startTimeUs = startTimeUs;
    this.preciseStart = preciseStart;
    this.hasDiscontinuitySequence = hasDiscontinuitySequence;
    this.discontinuitySequence = discontinuitySequence;
    this.mediaSequence = mediaSequence;
    this.version = version;
    this.targetDurationUs = targetDurationUs;
    this.partTargetDurationUs = partTargetDurationUs;
    this.hasEndTag = hasEndTag;
    this.hasProgramDateTime = hasProgramDateTime;
    this.protectionSchemes = protectionSchemes;
    this.segments = ImmutableList.copyOf(segments);
    this.trailingParts = ImmutableList.copyOf(trailingParts);
    this.renditionReports = ImmutableMap.copyOf(renditionReports);
    if (!trailingParts.isEmpty()) {
      Part lastPart = Iterables.getLast(trailingParts);
      durationUs = lastPart.relativeStartTimeUs + lastPart.durationUs;
    } else if (!segments.isEmpty()) {
      Segment lastSegment = Iterables.getLast(segments);
      durationUs = lastSegment.relativeStartTimeUs + lastSegment.durationUs;
    } else {
      durationUs = 0;
    }
    // From RFC 8216, section 4.4.2.2: If startOffsetUs is negative, it indicates the offset from
    // the end of the playlist. If the absolute value exceeds the duration of the playlist, it
    // indicates the beginning (if negative) or the end (if positive) of the playlist.
    this.startOffsetUs =
        startOffsetUs == C.TIME_UNSET
            ? C.TIME_UNSET
            : startOffsetUs >= 0
                ? min(durationUs, startOffsetUs)
                : max(0, durationUs + startOffsetUs);
    this.hasPositiveStartOffset = startOffsetUs >= 0;
    this.serverControl = serverControl;
  }

  @Override
  public HlsMediaPlaylist copy(List<StreamKey> streamKeys) {
    return this;
  }

  /**
   * Returns whether this playlist is newer than {@code other}.
   *
   * @param other The playlist to compare.
   * @return Whether this playlist is newer than {@code other}.
   */
  public boolean isNewerThan(@Nullable HlsMediaPlaylist other) {
    if (other == null || mediaSequence > other.mediaSequence) {
      return true;
    }
    if (mediaSequence < other.mediaSequence) {
      return false;
    }
    // The media sequences are equal.
    int segmentCountDifference = segments.size() - other.segments.size();
    if (segmentCountDifference != 0) {
      return segmentCountDifference > 0;
    }
    int partCount = trailingParts.size();
    int otherPartCount = other.trailingParts.size();
    return partCount > otherPartCount
        || (partCount == otherPartCount && hasEndTag && !other.hasEndTag);
  }

  /** Returns the result of adding the duration of the playlist to its start time. */
  public long getEndTimeUs() {
    return startTimeUs + durationUs;
  }

  /**
   * Returns a playlist identical to this one except for the start time, the discontinuity sequence
   * and {@code hasDiscontinuitySequence} values. The first two are set to the specified values,
   * {@code hasDiscontinuitySequence} is set to true.
   *
   * @param startTimeUs The start time for the returned playlist.
   * @param discontinuitySequence The discontinuity sequence for the returned playlist.
   * @return An identical playlist including the provided discontinuity and timing information.
   */
  public HlsMediaPlaylist copyWith(long startTimeUs, int discontinuitySequence) {
    return new HlsMediaPlaylist(
        playlistType,
        baseUri,
        tags,
        startOffsetUs,
        preciseStart,
        startTimeUs,
        /* hasDiscontinuitySequence= */ true,
        discontinuitySequence,
        mediaSequence,
        version,
        targetDurationUs,
        partTargetDurationUs,
        hasIndependentSegments,
        hasEndTag,
        hasProgramDateTime,
        protectionSchemes,
        segments,
        trailingParts,
        serverControl,
        renditionReports);
  }

  /**
   * Returns a playlist identical to this one except that an end tag is added. If an end tag is
   * already present then the playlist will return itself.
   */
  public HlsMediaPlaylist copyWithEndTag() {
    if (this.hasEndTag) {
      return this;
    }
    return new HlsMediaPlaylist(
        playlistType,
        baseUri,
        tags,
        startOffsetUs,
        preciseStart,
        startTimeUs,
        hasDiscontinuitySequence,
        discontinuitySequence,
        mediaSequence,
        version,
        targetDurationUs,
        partTargetDurationUs,
        hasIndependentSegments,
        /* hasEndTag= */ true,
        hasProgramDateTime,
        protectionSchemes,
        segments,
        trailingParts,
        serverControl,
        renditionReports);
  }
}