1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.exoplayer2.source.hls.playlist; 17 18 import androidx.annotation.IntDef; 19 import androidx.annotation.Nullable; 20 import com.google.android.exoplayer2.C; 21 import com.google.android.exoplayer2.drm.DrmInitData; 22 import com.google.android.exoplayer2.offline.StreamKey; 23 import java.lang.annotation.Documented; 24 import java.lang.annotation.Retention; 25 import java.lang.annotation.RetentionPolicy; 26 import java.util.Collections; 27 import java.util.List; 28 29 /** Represents an HLS media playlist. */ 30 public final class HlsMediaPlaylist extends HlsPlaylist { 31 32 /** Media segment reference. */ 33 @SuppressWarnings("ComparableType") 34 public static final class Segment implements Comparable<Long> { 35 36 /** 37 * The url of the segment. 38 */ 39 public final String url; 40 /** 41 * The media initialization section for this segment, as defined by #EXT-X-MAP. May be null if 42 * the media playlist does not define a media section for this segment. The same instance is 43 * used for all segments that share an EXT-X-MAP tag. 44 */ 45 @Nullable public final Segment initializationSegment; 46 /** The duration of the segment in microseconds, as defined by #EXTINF. */ 47 public final long durationUs; 48 /** The human readable title of the segment. */ 49 public final String title; 50 /** 51 * The number of #EXT-X-DISCONTINUITY tags in the playlist before the segment. 52 */ 53 public final int relativeDiscontinuitySequence; 54 /** 55 * The start time of the segment in microseconds, relative to the start of the playlist. 56 */ 57 public final long relativeStartTimeUs; 58 /** 59 * DRM initialization data for sample decryption, or null if the segment does not use CDM-DRM 60 * protection. 61 */ 62 @Nullable public final DrmInitData drmInitData; 63 /** 64 * The encryption identity key uri as defined by #EXT-X-KEY, or null if the segment does not use 65 * full segment encryption with identity key. 66 */ 67 @Nullable public final String fullSegmentEncryptionKeyUri; 68 /** 69 * The encryption initialization vector as defined by #EXT-X-KEY, or null if the segment is not 70 * encrypted. 71 */ 72 @Nullable public final String encryptionIV; 73 /** The segment's byte range offset, as defined by #EXT-X-BYTERANGE. */ 74 public final long byteRangeOffset; 75 /** 76 * The segment's byte range length, as defined by #EXT-X-BYTERANGE, or {@link C#LENGTH_UNSET} if 77 * no byte range is specified. 78 */ 79 public final long byteRangeLength; 80 81 /** Whether the segment is tagged with #EXT-X-GAP. */ 82 public final boolean hasGapTag; 83 84 /** 85 * @param uri See {@link #url}. 86 * @param byteRangeOffset See {@link #byteRangeOffset}. 87 * @param byteRangeLength See {@link #byteRangeLength}. 88 * @param fullSegmentEncryptionKeyUri See {@link #fullSegmentEncryptionKeyUri}. 89 * @param encryptionIV See {@link #encryptionIV}. 90 */ Segment( String uri, long byteRangeOffset, long byteRangeLength, @Nullable String fullSegmentEncryptionKeyUri, @Nullable String encryptionIV)91 public Segment( 92 String uri, 93 long byteRangeOffset, 94 long byteRangeLength, 95 @Nullable String fullSegmentEncryptionKeyUri, 96 @Nullable String encryptionIV) { 97 this( 98 uri, 99 /* initializationSegment= */ null, 100 /* title= */ "", 101 /* durationUs= */ 0, 102 /* relativeDiscontinuitySequence= */ -1, 103 /* relativeStartTimeUs= */ C.TIME_UNSET, 104 /* drmInitData= */ null, 105 fullSegmentEncryptionKeyUri, 106 encryptionIV, 107 byteRangeOffset, 108 byteRangeLength, 109 /* hasGapTag= */ false); 110 } 111 112 /** 113 * @param url See {@link #url}. 114 * @param initializationSegment See {@link #initializationSegment}. 115 * @param title See {@link #title}. 116 * @param durationUs See {@link #durationUs}. 117 * @param relativeDiscontinuitySequence See {@link #relativeDiscontinuitySequence}. 118 * @param relativeStartTimeUs See {@link #relativeStartTimeUs}. 119 * @param drmInitData See {@link #drmInitData}. 120 * @param fullSegmentEncryptionKeyUri See {@link #fullSegmentEncryptionKeyUri}. 121 * @param encryptionIV See {@link #encryptionIV}. 122 * @param byteRangeOffset See {@link #byteRangeOffset}. 123 * @param byteRangeLength See {@link #byteRangeLength}. 124 * @param hasGapTag See {@link #hasGapTag}. 125 */ 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)126 public Segment( 127 String url, 128 @Nullable Segment initializationSegment, 129 String title, 130 long durationUs, 131 int relativeDiscontinuitySequence, 132 long relativeStartTimeUs, 133 @Nullable DrmInitData drmInitData, 134 @Nullable String fullSegmentEncryptionKeyUri, 135 @Nullable String encryptionIV, 136 long byteRangeOffset, 137 long byteRangeLength, 138 boolean hasGapTag) { 139 this.url = url; 140 this.initializationSegment = initializationSegment; 141 this.title = title; 142 this.durationUs = durationUs; 143 this.relativeDiscontinuitySequence = relativeDiscontinuitySequence; 144 this.relativeStartTimeUs = relativeStartTimeUs; 145 this.drmInitData = drmInitData; 146 this.fullSegmentEncryptionKeyUri = fullSegmentEncryptionKeyUri; 147 this.encryptionIV = encryptionIV; 148 this.byteRangeOffset = byteRangeOffset; 149 this.byteRangeLength = byteRangeLength; 150 this.hasGapTag = hasGapTag; 151 } 152 153 @Override compareTo(Long relativeStartTimeUs)154 public int compareTo(Long relativeStartTimeUs) { 155 return this.relativeStartTimeUs > relativeStartTimeUs 156 ? 1 : (this.relativeStartTimeUs < relativeStartTimeUs ? -1 : 0); 157 } 158 159 } 160 161 /** 162 * Type of the playlist, as defined by #EXT-X-PLAYLIST-TYPE. One of {@link 163 * #PLAYLIST_TYPE_UNKNOWN}, {@link #PLAYLIST_TYPE_VOD} or {@link #PLAYLIST_TYPE_EVENT}. 164 */ 165 @Documented 166 @Retention(RetentionPolicy.SOURCE) 167 @IntDef({PLAYLIST_TYPE_UNKNOWN, PLAYLIST_TYPE_VOD, PLAYLIST_TYPE_EVENT}) 168 public @interface PlaylistType {} 169 170 public static final int PLAYLIST_TYPE_UNKNOWN = 0; 171 public static final int PLAYLIST_TYPE_VOD = 1; 172 public static final int PLAYLIST_TYPE_EVENT = 2; 173 174 /** 175 * The type of the playlist. See {@link PlaylistType}. 176 */ 177 @PlaylistType public final int playlistType; 178 /** 179 * The start offset in microseconds, as defined by #EXT-X-START. 180 */ 181 public final long startOffsetUs; 182 /** 183 * If {@link #hasProgramDateTime} is true, contains the datetime as microseconds since epoch. 184 * Otherwise, contains the aggregated duration of removed segments up to this snapshot of the 185 * playlist. 186 */ 187 public final long startTimeUs; 188 /** 189 * Whether the playlist contains the #EXT-X-DISCONTINUITY-SEQUENCE tag. 190 */ 191 public final boolean hasDiscontinuitySequence; 192 /** 193 * The discontinuity sequence number of the first media segment in the playlist, as defined by 194 * #EXT-X-DISCONTINUITY-SEQUENCE. 195 */ 196 public final int discontinuitySequence; 197 /** 198 * The media sequence number of the first media segment in the playlist, as defined by 199 * #EXT-X-MEDIA-SEQUENCE. 200 */ 201 public final long mediaSequence; 202 /** 203 * The compatibility version, as defined by #EXT-X-VERSION. 204 */ 205 public final int version; 206 /** 207 * The target duration in microseconds, as defined by #EXT-X-TARGETDURATION. 208 */ 209 public final long targetDurationUs; 210 /** 211 * Whether the playlist contains the #EXT-X-ENDLIST tag. 212 */ 213 public final boolean hasEndTag; 214 /** 215 * Whether the playlist contains a #EXT-X-PROGRAM-DATE-TIME tag. 216 */ 217 public final boolean hasProgramDateTime; 218 /** 219 * Contains the CDM protection schemes used by segments in this playlist. Does not contain any key 220 * acquisition data. Null if none of the segments in the playlist is CDM-encrypted. 221 */ 222 @Nullable public final DrmInitData protectionSchemes; 223 /** 224 * The list of segments in the playlist. 225 */ 226 public final List<Segment> segments; 227 /** 228 * The total duration of the playlist in microseconds. 229 */ 230 public final long durationUs; 231 232 /** 233 * @param playlistType See {@link #playlistType}. 234 * @param baseUri See {@link #baseUri}. 235 * @param tags See {@link #tags}. 236 * @param startOffsetUs See {@link #startOffsetUs}. 237 * @param startTimeUs See {@link #startTimeUs}. 238 * @param hasDiscontinuitySequence See {@link #hasDiscontinuitySequence}. 239 * @param discontinuitySequence See {@link #discontinuitySequence}. 240 * @param mediaSequence See {@link #mediaSequence}. 241 * @param version See {@link #version}. 242 * @param targetDurationUs See {@link #targetDurationUs}. 243 * @param hasIndependentSegments See {@link #hasIndependentSegments}. 244 * @param hasEndTag See {@link #hasEndTag}. 245 * @param protectionSchemes See {@link #protectionSchemes}. 246 * @param hasProgramDateTime See {@link #hasProgramDateTime}. 247 * @param segments See {@link #segments}. 248 */ HlsMediaPlaylist( @laylistType int playlistType, String baseUri, List<String> tags, long startOffsetUs, long startTimeUs, boolean hasDiscontinuitySequence, int discontinuitySequence, long mediaSequence, int version, long targetDurationUs, boolean hasIndependentSegments, boolean hasEndTag, boolean hasProgramDateTime, @Nullable DrmInitData protectionSchemes, List<Segment> segments)249 public HlsMediaPlaylist( 250 @PlaylistType int playlistType, 251 String baseUri, 252 List<String> tags, 253 long startOffsetUs, 254 long startTimeUs, 255 boolean hasDiscontinuitySequence, 256 int discontinuitySequence, 257 long mediaSequence, 258 int version, 259 long targetDurationUs, 260 boolean hasIndependentSegments, 261 boolean hasEndTag, 262 boolean hasProgramDateTime, 263 @Nullable DrmInitData protectionSchemes, 264 List<Segment> segments) { 265 super(baseUri, tags, hasIndependentSegments); 266 this.playlistType = playlistType; 267 this.startTimeUs = startTimeUs; 268 this.hasDiscontinuitySequence = hasDiscontinuitySequence; 269 this.discontinuitySequence = discontinuitySequence; 270 this.mediaSequence = mediaSequence; 271 this.version = version; 272 this.targetDurationUs = targetDurationUs; 273 this.hasEndTag = hasEndTag; 274 this.hasProgramDateTime = hasProgramDateTime; 275 this.protectionSchemes = protectionSchemes; 276 this.segments = Collections.unmodifiableList(segments); 277 if (!segments.isEmpty()) { 278 Segment last = segments.get(segments.size() - 1); 279 durationUs = last.relativeStartTimeUs + last.durationUs; 280 } else { 281 durationUs = 0; 282 } 283 this.startOffsetUs = startOffsetUs == C.TIME_UNSET ? C.TIME_UNSET 284 : startOffsetUs >= 0 ? startOffsetUs : durationUs + startOffsetUs; 285 } 286 287 @Override copy(List<StreamKey> streamKeys)288 public HlsMediaPlaylist copy(List<StreamKey> streamKeys) { 289 return this; 290 } 291 292 /** 293 * Returns whether this playlist is newer than {@code other}. 294 * 295 * @param other The playlist to compare. 296 * @return Whether this playlist is newer than {@code other}. 297 */ isNewerThan(HlsMediaPlaylist other)298 public boolean isNewerThan(HlsMediaPlaylist other) { 299 if (other == null || mediaSequence > other.mediaSequence) { 300 return true; 301 } 302 if (mediaSequence < other.mediaSequence) { 303 return false; 304 } 305 // The media sequences are equal. 306 int segmentCount = segments.size(); 307 int otherSegmentCount = other.segments.size(); 308 return segmentCount > otherSegmentCount 309 || (segmentCount == otherSegmentCount && hasEndTag && !other.hasEndTag); 310 } 311 312 /** 313 * Returns the result of adding the duration of the playlist to its start time. 314 */ getEndTimeUs()315 public long getEndTimeUs() { 316 return startTimeUs + durationUs; 317 } 318 319 /** 320 * Returns a playlist identical to this one except for the start time, the discontinuity sequence 321 * and {@code hasDiscontinuitySequence} values. The first two are set to the specified values, 322 * {@code hasDiscontinuitySequence} is set to true. 323 * 324 * @param startTimeUs The start time for the returned playlist. 325 * @param discontinuitySequence The discontinuity sequence for the returned playlist. 326 * @return An identical playlist including the provided discontinuity and timing information. 327 */ copyWith(long startTimeUs, int discontinuitySequence)328 public HlsMediaPlaylist copyWith(long startTimeUs, int discontinuitySequence) { 329 return new HlsMediaPlaylist( 330 playlistType, 331 baseUri, 332 tags, 333 startOffsetUs, 334 startTimeUs, 335 /* hasDiscontinuitySequence= */ true, 336 discontinuitySequence, 337 mediaSequence, 338 version, 339 targetDurationUs, 340 hasIndependentSegments, 341 hasEndTag, 342 hasProgramDateTime, 343 protectionSchemes, 344 segments); 345 } 346 347 /** 348 * Returns a playlist identical to this one except that an end tag is added. If an end tag is 349 * already present then the playlist will return itself. 350 */ copyWithEndTag()351 public HlsMediaPlaylist copyWithEndTag() { 352 if (this.hasEndTag) { 353 return this; 354 } 355 return new HlsMediaPlaylist( 356 playlistType, 357 baseUri, 358 tags, 359 startOffsetUs, 360 startTimeUs, 361 hasDiscontinuitySequence, 362 discontinuitySequence, 363 mediaSequence, 364 version, 365 targetDurationUs, 366 hasIndependentSegments, 367 /* hasEndTag= */ true, 368 hasProgramDateTime, 369 protectionSchemes, 370 segments); 371 } 372 373 } 374