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 org.mozilla.thirdparty.com.google.android.exoplayer2.source.hls; 17 18 import static java.lang.annotation.RetentionPolicy.SOURCE; 19 20 import android.net.Uri; 21 import android.os.Handler; 22 import androidx.annotation.IntDef; 23 import androidx.annotation.Nullable; 24 import org.mozilla.thirdparty.com.google.android.exoplayer2.C; 25 import org.mozilla.thirdparty.com.google.android.exoplayer2.ExoPlayerLibraryInfo; 26 import org.mozilla.thirdparty.com.google.android.exoplayer2.drm.DrmSession; 27 import org.mozilla.thirdparty.com.google.android.exoplayer2.drm.DrmSessionManager; 28 import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.Extractor; 29 import org.mozilla.thirdparty.com.google.android.exoplayer2.offline.StreamKey; 30 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.BaseMediaSource; 31 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory; 32 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.DefaultCompositeSequenceableLoaderFactory; 33 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.MediaPeriod; 34 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.MediaSource; 35 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.MediaSourceEventListener; 36 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; 37 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.MediaSourceFactory; 38 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.SequenceableLoader; 39 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.SinglePeriodTimeline; 40 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.hls.playlist.DefaultHlsPlaylistParserFactory; 41 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.hls.playlist.DefaultHlsPlaylistTracker; 42 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.hls.playlist.FilteringHlsPlaylistParserFactory; 43 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; 44 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParserFactory; 45 import org.mozilla.thirdparty.com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker; 46 import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.Allocator; 47 import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSource; 48 import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; 49 import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; 50 import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.TransferListener; 51 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions; 52 import java.io.IOException; 53 import java.lang.annotation.Documented; 54 import java.lang.annotation.Retention; 55 import java.util.List; 56 57 /** An HLS {@link MediaSource}. */ 58 public final class HlsMediaSource extends BaseMediaSource 59 implements HlsPlaylistTracker.PrimaryPlaylistListener { 60 61 static { 62 ExoPlayerLibraryInfo.registerModule("goog.exo.hls"); 63 } 64 65 /** 66 * The types of metadata that can be extracted from HLS streams. 67 * 68 * <p>Allowed values: 69 * 70 * <ul> 71 * <li>{@link #METADATA_TYPE_ID3} 72 * <li>{@link #METADATA_TYPE_EMSG} 73 * </ul> 74 * 75 * <p>See {@link Factory#setMetadataType(int)}. 76 */ 77 @Documented 78 @Retention(SOURCE) 79 @IntDef({METADATA_TYPE_ID3, METADATA_TYPE_EMSG}) 80 public @interface MetadataType {} 81 82 /** Type for ID3 metadata in HLS streams. */ 83 public static final int METADATA_TYPE_ID3 = 1; 84 /** Type for ESMG metadata in HLS streams. */ 85 public static final int METADATA_TYPE_EMSG = 3; 86 87 /** Factory for {@link HlsMediaSource}s. */ 88 public static final class Factory implements MediaSourceFactory { 89 90 private final HlsDataSourceFactory hlsDataSourceFactory; 91 92 private HlsExtractorFactory extractorFactory; 93 private HlsPlaylistParserFactory playlistParserFactory; 94 @Nullable private List<StreamKey> streamKeys; 95 private HlsPlaylistTracker.Factory playlistTrackerFactory; 96 private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; 97 private DrmSessionManager<?> drmSessionManager; 98 private LoadErrorHandlingPolicy loadErrorHandlingPolicy; 99 private boolean allowChunklessPreparation; 100 @MetadataType private int metadataType; 101 private boolean useSessionKeys; 102 private boolean isCreateCalled; 103 @Nullable private Object tag; 104 105 /** 106 * Creates a new factory for {@link HlsMediaSource}s. 107 * 108 * @param dataSourceFactory A data source factory that will be wrapped by a {@link 109 * DefaultHlsDataSourceFactory} to create {@link DataSource}s for manifests, segments and 110 * keys. 111 */ Factory(DataSource.Factory dataSourceFactory)112 public Factory(DataSource.Factory dataSourceFactory) { 113 this(new DefaultHlsDataSourceFactory(dataSourceFactory)); 114 } 115 116 /** 117 * Creates a new factory for {@link HlsMediaSource}s. 118 * 119 * @param hlsDataSourceFactory An {@link HlsDataSourceFactory} for {@link DataSource}s for 120 * manifests, segments and keys. 121 */ Factory(HlsDataSourceFactory hlsDataSourceFactory)122 public Factory(HlsDataSourceFactory hlsDataSourceFactory) { 123 this.hlsDataSourceFactory = Assertions.checkNotNull(hlsDataSourceFactory); 124 playlistParserFactory = new DefaultHlsPlaylistParserFactory(); 125 playlistTrackerFactory = DefaultHlsPlaylistTracker.FACTORY; 126 extractorFactory = HlsExtractorFactory.DEFAULT; 127 drmSessionManager = DrmSessionManager.getDummyDrmSessionManager(); 128 loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy(); 129 compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory(); 130 metadataType = METADATA_TYPE_ID3; 131 } 132 133 /** 134 * Sets a tag for the media source which will be published in the {@link 135 * org.mozilla.thirdparty.com.google.android.exoplayer2.Timeline} of the source as {@link 136 * org.mozilla.thirdparty.com.google.android.exoplayer2.Timeline.Window#tag}. 137 * 138 * @param tag A tag for the media source. 139 * @return This factory, for convenience. 140 * @throws IllegalStateException If one of the {@code create} methods has already been called. 141 */ setTag(@ullable Object tag)142 public Factory setTag(@Nullable Object tag) { 143 Assertions.checkState(!isCreateCalled); 144 this.tag = tag; 145 return this; 146 } 147 148 /** 149 * Sets the factory for {@link Extractor}s for the segments. The default value is {@link 150 * HlsExtractorFactory#DEFAULT}. 151 * 152 * @param extractorFactory An {@link HlsExtractorFactory} for {@link Extractor}s for the 153 * segments. 154 * @return This factory, for convenience. 155 * @throws IllegalStateException If one of the {@code create} methods has already been called. 156 */ setExtractorFactory(HlsExtractorFactory extractorFactory)157 public Factory setExtractorFactory(HlsExtractorFactory extractorFactory) { 158 Assertions.checkState(!isCreateCalled); 159 this.extractorFactory = Assertions.checkNotNull(extractorFactory); 160 return this; 161 } 162 163 /** 164 * Sets the {@link LoadErrorHandlingPolicy}. The default value is created by calling {@link 165 * DefaultLoadErrorHandlingPolicy#DefaultLoadErrorHandlingPolicy()}. 166 * 167 * <p>Calling this method overrides any calls to {@link #setMinLoadableRetryCount(int)}. 168 * 169 * @param loadErrorHandlingPolicy A {@link LoadErrorHandlingPolicy}. 170 * @return This factory, for convenience. 171 * @throws IllegalStateException If one of the {@code create} methods has already been called. 172 */ setLoadErrorHandlingPolicy(LoadErrorHandlingPolicy loadErrorHandlingPolicy)173 public Factory setLoadErrorHandlingPolicy(LoadErrorHandlingPolicy loadErrorHandlingPolicy) { 174 Assertions.checkState(!isCreateCalled); 175 this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; 176 return this; 177 } 178 179 /** 180 * Sets the minimum number of times to retry if a loading error occurs. The default value is 181 * {@link DefaultLoadErrorHandlingPolicy#DEFAULT_MIN_LOADABLE_RETRY_COUNT}. 182 * 183 * <p>Calling this method is equivalent to calling {@link #setLoadErrorHandlingPolicy} with 184 * {@link DefaultLoadErrorHandlingPolicy#DefaultLoadErrorHandlingPolicy(int) 185 * DefaultLoadErrorHandlingPolicy(minLoadableRetryCount)} 186 * 187 * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. 188 * @return This factory, for convenience. 189 * @throws IllegalStateException If one of the {@code create} methods has already been called. 190 * @deprecated Use {@link #setLoadErrorHandlingPolicy(LoadErrorHandlingPolicy)} instead. 191 */ 192 @Deprecated setMinLoadableRetryCount(int minLoadableRetryCount)193 public Factory setMinLoadableRetryCount(int minLoadableRetryCount) { 194 Assertions.checkState(!isCreateCalled); 195 this.loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy(minLoadableRetryCount); 196 return this; 197 } 198 199 /** 200 * Sets the factory from which playlist parsers will be obtained. The default value is a {@link 201 * DefaultHlsPlaylistParserFactory}. 202 * 203 * @param playlistParserFactory An {@link HlsPlaylistParserFactory}. 204 * @return This factory, for convenience. 205 * @throws IllegalStateException If one of the {@code create} methods has already been called. 206 */ setPlaylistParserFactory(HlsPlaylistParserFactory playlistParserFactory)207 public Factory setPlaylistParserFactory(HlsPlaylistParserFactory playlistParserFactory) { 208 Assertions.checkState(!isCreateCalled); 209 this.playlistParserFactory = Assertions.checkNotNull(playlistParserFactory); 210 return this; 211 } 212 213 /** 214 * Sets the {@link HlsPlaylistTracker} factory. The default value is {@link 215 * DefaultHlsPlaylistTracker#FACTORY}. 216 * 217 * @param playlistTrackerFactory A factory for {@link HlsPlaylistTracker} instances. 218 * @return This factory, for convenience. 219 * @throws IllegalStateException If one of the {@code create} methods has already been called. 220 */ setPlaylistTrackerFactory(HlsPlaylistTracker.Factory playlistTrackerFactory)221 public Factory setPlaylistTrackerFactory(HlsPlaylistTracker.Factory playlistTrackerFactory) { 222 Assertions.checkState(!isCreateCalled); 223 this.playlistTrackerFactory = Assertions.checkNotNull(playlistTrackerFactory); 224 return this; 225 } 226 227 /** 228 * Sets the factory to create composite {@link SequenceableLoader}s for when this media source 229 * loads data from multiple streams (video, audio etc...). The default is an instance of {@link 230 * DefaultCompositeSequenceableLoaderFactory}. 231 * 232 * @param compositeSequenceableLoaderFactory A factory to create composite {@link 233 * SequenceableLoader}s for when this media source loads data from multiple streams (video, 234 * audio etc...). 235 * @return This factory, for convenience. 236 * @throws IllegalStateException If one of the {@code create} methods has already been called. 237 */ setCompositeSequenceableLoaderFactory( CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory)238 public Factory setCompositeSequenceableLoaderFactory( 239 CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory) { 240 Assertions.checkState(!isCreateCalled); 241 this.compositeSequenceableLoaderFactory = 242 Assertions.checkNotNull(compositeSequenceableLoaderFactory); 243 return this; 244 } 245 246 /** 247 * Sets whether chunkless preparation is allowed. If true, preparation without chunk downloads 248 * will be enabled for streams that provide sufficient information in their master playlist. 249 * 250 * @param allowChunklessPreparation Whether chunkless preparation is allowed. 251 * @return This factory, for convenience. 252 * @throws IllegalStateException If one of the {@code create} methods has already been called. 253 */ setAllowChunklessPreparation(boolean allowChunklessPreparation)254 public Factory setAllowChunklessPreparation(boolean allowChunklessPreparation) { 255 Assertions.checkState(!isCreateCalled); 256 this.allowChunklessPreparation = allowChunklessPreparation; 257 return this; 258 } 259 260 /** 261 * Sets the type of metadata to extract from the HLS source (defaults to {@link 262 * #METADATA_TYPE_ID3}). 263 * 264 * <p>HLS supports in-band ID3 in both TS and fMP4 streams, but in the fMP4 case the data is 265 * wrapped in an EMSG box [<a href="https://aomediacodec.github.io/av1-id3/">spec</a>]. 266 * 267 * <p>If this is set to {@link #METADATA_TYPE_ID3} then raw ID3 metadata of will be extracted 268 * from TS sources. From fMP4 streams EMSGs containing metadata of this type (in the variant 269 * stream only) will be unwrapped to expose the inner data. All other in-band metadata will be 270 * dropped. 271 * 272 * <p>If this is set to {@link #METADATA_TYPE_EMSG} then all EMSG data from the fMP4 variant 273 * stream will be extracted. No metadata will be extracted from TS streams, since they don't 274 * support EMSG. 275 * 276 * @param metadataType The type of metadata to extract. 277 * @return This factory, for convenience. 278 */ setMetadataType(@etadataType int metadataType)279 public Factory setMetadataType(@MetadataType int metadataType) { 280 Assertions.checkState(!isCreateCalled); 281 this.metadataType = metadataType; 282 return this; 283 } 284 285 /** 286 * Sets whether to use #EXT-X-SESSION-KEY tags provided in the master playlist. If enabled, it's 287 * assumed that any single session key declared in the master playlist can be used to obtain all 288 * of the keys required for playback. For media where this is not true, this option should not 289 * be enabled. 290 * 291 * @param useSessionKeys Whether to use #EXT-X-SESSION-KEY tags. 292 * @return This factory, for convenience. 293 */ setUseSessionKeys(boolean useSessionKeys)294 public Factory setUseSessionKeys(boolean useSessionKeys) { 295 this.useSessionKeys = useSessionKeys; 296 return this; 297 } 298 299 /** 300 * @deprecated Use {@link #createMediaSource(Uri)} and {@link #addEventListener(Handler, 301 * MediaSourceEventListener)} instead. 302 */ 303 @Deprecated createMediaSource( Uri playlistUri, @Nullable Handler eventHandler, @Nullable MediaSourceEventListener eventListener)304 public HlsMediaSource createMediaSource( 305 Uri playlistUri, 306 @Nullable Handler eventHandler, 307 @Nullable MediaSourceEventListener eventListener) { 308 HlsMediaSource mediaSource = createMediaSource(playlistUri); 309 if (eventHandler != null && eventListener != null) { 310 mediaSource.addEventListener(eventHandler, eventListener); 311 } 312 return mediaSource; 313 } 314 315 /** 316 * Sets the {@link DrmSessionManager} to use for acquiring {@link DrmSession DrmSessions}. The 317 * default value is {@link DrmSessionManager#DUMMY}. 318 * 319 * @param drmSessionManager The {@link DrmSessionManager}. 320 * @return This factory, for convenience. 321 * @throws IllegalStateException If one of the {@code create} methods has already been called. 322 */ 323 @Override setDrmSessionManager(DrmSessionManager<?> drmSessionManager)324 public Factory setDrmSessionManager(DrmSessionManager<?> drmSessionManager) { 325 Assertions.checkState(!isCreateCalled); 326 this.drmSessionManager = drmSessionManager; 327 return this; 328 } 329 330 /** 331 * Returns a new {@link HlsMediaSource} using the current parameters. 332 * 333 * @return The new {@link HlsMediaSource}. 334 */ 335 @Override createMediaSource(Uri playlistUri)336 public HlsMediaSource createMediaSource(Uri playlistUri) { 337 isCreateCalled = true; 338 if (streamKeys != null) { 339 playlistParserFactory = 340 new FilteringHlsPlaylistParserFactory(playlistParserFactory, streamKeys); 341 } 342 return new HlsMediaSource( 343 playlistUri, 344 hlsDataSourceFactory, 345 extractorFactory, 346 compositeSequenceableLoaderFactory, 347 drmSessionManager, 348 loadErrorHandlingPolicy, 349 playlistTrackerFactory.createTracker( 350 hlsDataSourceFactory, loadErrorHandlingPolicy, playlistParserFactory), 351 allowChunklessPreparation, 352 metadataType, 353 useSessionKeys, 354 tag); 355 } 356 357 @Override setStreamKeys(List<StreamKey> streamKeys)358 public Factory setStreamKeys(List<StreamKey> streamKeys) { 359 Assertions.checkState(!isCreateCalled); 360 this.streamKeys = streamKeys; 361 return this; 362 } 363 364 @Override getSupportedTypes()365 public int[] getSupportedTypes() { 366 return new int[] {C.TYPE_HLS}; 367 } 368 369 } 370 371 private final HlsExtractorFactory extractorFactory; 372 private final Uri manifestUri; 373 private final HlsDataSourceFactory dataSourceFactory; 374 private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; 375 private final DrmSessionManager<?> drmSessionManager; 376 private final LoadErrorHandlingPolicy loadErrorHandlingPolicy; 377 private final boolean allowChunklessPreparation; 378 private final @MetadataType int metadataType; 379 private final boolean useSessionKeys; 380 private final HlsPlaylistTracker playlistTracker; 381 @Nullable private final Object tag; 382 383 @Nullable private TransferListener mediaTransferListener; 384 HlsMediaSource( Uri manifestUri, HlsDataSourceFactory dataSourceFactory, HlsExtractorFactory extractorFactory, CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, DrmSessionManager<?> drmSessionManager, LoadErrorHandlingPolicy loadErrorHandlingPolicy, HlsPlaylistTracker playlistTracker, boolean allowChunklessPreparation, @MetadataType int metadataType, boolean useSessionKeys, @Nullable Object tag)385 private HlsMediaSource( 386 Uri manifestUri, 387 HlsDataSourceFactory dataSourceFactory, 388 HlsExtractorFactory extractorFactory, 389 CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, 390 DrmSessionManager<?> drmSessionManager, 391 LoadErrorHandlingPolicy loadErrorHandlingPolicy, 392 HlsPlaylistTracker playlistTracker, 393 boolean allowChunklessPreparation, 394 @MetadataType int metadataType, 395 boolean useSessionKeys, 396 @Nullable Object tag) { 397 this.manifestUri = manifestUri; 398 this.dataSourceFactory = dataSourceFactory; 399 this.extractorFactory = extractorFactory; 400 this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; 401 this.drmSessionManager = drmSessionManager; 402 this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; 403 this.playlistTracker = playlistTracker; 404 this.allowChunklessPreparation = allowChunklessPreparation; 405 this.metadataType = metadataType; 406 this.useSessionKeys = useSessionKeys; 407 this.tag = tag; 408 } 409 410 @Override 411 @Nullable getTag()412 public Object getTag() { 413 return tag; 414 } 415 416 @Override prepareSourceInternal(@ullable TransferListener mediaTransferListener)417 protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { 418 this.mediaTransferListener = mediaTransferListener; 419 drmSessionManager.prepare(); 420 EventDispatcher eventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null); 421 playlistTracker.start(manifestUri, eventDispatcher, /* listener= */ this); 422 } 423 424 @Override maybeThrowSourceInfoRefreshError()425 public void maybeThrowSourceInfoRefreshError() throws IOException { 426 playlistTracker.maybeThrowPrimaryPlaylistRefreshError(); 427 } 428 429 @Override createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs)430 public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { 431 EventDispatcher eventDispatcher = createEventDispatcher(id); 432 return new HlsMediaPeriod( 433 extractorFactory, 434 playlistTracker, 435 dataSourceFactory, 436 mediaTransferListener, 437 drmSessionManager, 438 loadErrorHandlingPolicy, 439 eventDispatcher, 440 allocator, 441 compositeSequenceableLoaderFactory, 442 allowChunklessPreparation, 443 metadataType, 444 useSessionKeys); 445 } 446 447 @Override releasePeriod(MediaPeriod mediaPeriod)448 public void releasePeriod(MediaPeriod mediaPeriod) { 449 ((HlsMediaPeriod) mediaPeriod).release(); 450 } 451 452 @Override releaseSourceInternal()453 protected void releaseSourceInternal() { 454 playlistTracker.stop(); 455 drmSessionManager.release(); 456 } 457 458 @Override onPrimaryPlaylistRefreshed(HlsMediaPlaylist playlist)459 public void onPrimaryPlaylistRefreshed(HlsMediaPlaylist playlist) { 460 SinglePeriodTimeline timeline; 461 long windowStartTimeMs = playlist.hasProgramDateTime ? C.usToMs(playlist.startTimeUs) 462 : C.TIME_UNSET; 463 // For playlist types EVENT and VOD we know segments are never removed, so the presentation 464 // started at the same time as the window. Otherwise, we don't know the presentation start time. 465 long presentationStartTimeMs = 466 playlist.playlistType == HlsMediaPlaylist.PLAYLIST_TYPE_EVENT 467 || playlist.playlistType == HlsMediaPlaylist.PLAYLIST_TYPE_VOD 468 ? windowStartTimeMs 469 : C.TIME_UNSET; 470 long windowDefaultStartPositionUs = playlist.startOffsetUs; 471 // masterPlaylist is non-null because the first playlist has been fetched by now. 472 HlsManifest manifest = 473 new HlsManifest(Assertions.checkNotNull(playlistTracker.getMasterPlaylist()), playlist); 474 if (playlistTracker.isLive()) { 475 long offsetFromInitialStartTimeUs = 476 playlist.startTimeUs - playlistTracker.getInitialStartTimeUs(); 477 long periodDurationUs = 478 playlist.hasEndTag ? offsetFromInitialStartTimeUs + playlist.durationUs : C.TIME_UNSET; 479 List<HlsMediaPlaylist.Segment> segments = playlist.segments; 480 if (windowDefaultStartPositionUs == C.TIME_UNSET) { 481 windowDefaultStartPositionUs = 0; 482 if (!segments.isEmpty()) { 483 int defaultStartSegmentIndex = Math.max(0, segments.size() - 3); 484 // We attempt to set the default start position to be at least twice the target duration 485 // behind the live edge. 486 long minStartPositionUs = playlist.durationUs - playlist.targetDurationUs * 2; 487 while (defaultStartSegmentIndex > 0 488 && segments.get(defaultStartSegmentIndex).relativeStartTimeUs > minStartPositionUs) { 489 defaultStartSegmentIndex--; 490 } 491 windowDefaultStartPositionUs = segments.get(defaultStartSegmentIndex).relativeStartTimeUs; 492 } 493 } 494 timeline = 495 new SinglePeriodTimeline( 496 presentationStartTimeMs, 497 windowStartTimeMs, 498 periodDurationUs, 499 /* windowDurationUs= */ playlist.durationUs, 500 /* windowPositionInPeriodUs= */ offsetFromInitialStartTimeUs, 501 windowDefaultStartPositionUs, 502 /* isSeekable= */ true, 503 /* isDynamic= */ !playlist.hasEndTag, 504 /* isLive= */ true, 505 manifest, 506 tag); 507 } else /* not live */ { 508 if (windowDefaultStartPositionUs == C.TIME_UNSET) { 509 windowDefaultStartPositionUs = 0; 510 } 511 timeline = 512 new SinglePeriodTimeline( 513 presentationStartTimeMs, 514 windowStartTimeMs, 515 /* periodDurationUs= */ playlist.durationUs, 516 /* windowDurationUs= */ playlist.durationUs, 517 /* windowPositionInPeriodUs= */ 0, 518 windowDefaultStartPositionUs, 519 /* isSeekable= */ true, 520 /* isDynamic= */ false, 521 /* isLive= */ false, 522 manifest, 523 tag); 524 } 525 refreshSourceInfo(timeline); 526 } 527 528 } 529