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