blob: 418fb171f58c29f856cc12f705c40d3e482319ad [file] [log] [blame]
/*
* 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 com.google.android.exoplayer2.source.smoothstreaming;
import android.net.Uri;
import android.os.Handler;
import android.os.SystemClock;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.offline.FilteringManifestParser;
import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.source.BaseMediaSource;
import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory;
import com.google.android.exoplayer2.source.DefaultCompositeSequenceableLoaderFactory;
import com.google.android.exoplayer2.source.LoadEventInfo;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.source.SequenceableLoader;
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsUtil;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.Loader;
import com.google.android.exoplayer2.upstream.Loader.LoadErrorAction;
import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** A SmoothStreaming {@link MediaSource}. */
public final class SsMediaSource extends BaseMediaSource
implements Loader.Callback<ParsingLoadable<SsManifest>> {
static {
ExoPlayerLibraryInfo.registerModule("goog.exo.smoothstreaming");
}
/** Factory for {@link SsMediaSource}. */
public static final class Factory implements MediaSourceFactory {
private final SsChunkSource.Factory chunkSourceFactory;
@Nullable private final DataSource.Factory manifestDataSourceFactory;
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private DrmSessionManager drmSessionManager;
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private long livePresentationDelayMs;
@Nullable private ParsingLoadable.Parser<? extends SsManifest> manifestParser;
private List<StreamKey> streamKeys;
@Nullable private Object tag;
/**
* Creates a new factory for {@link SsMediaSource}s.
*
* @param dataSourceFactory A factory for {@link DataSource} instances that will be used to load
* manifest and media data.
*/
public Factory(DataSource.Factory dataSourceFactory) {
this(new DefaultSsChunkSource.Factory(dataSourceFactory), dataSourceFactory);
}
/**
* Creates a new factory for {@link SsMediaSource}s.
*
* @param chunkSourceFactory A factory for {@link SsChunkSource} instances.
* @param manifestDataSourceFactory A factory for {@link DataSource} instances that will be used
* to load (and refresh) the manifest. May be {@code null} if the factory will only ever be
* used to create create media sources with sideloaded manifests via {@link
* #createMediaSource(SsManifest, Handler, MediaSourceEventListener)}.
*/
public Factory(
SsChunkSource.Factory chunkSourceFactory,
@Nullable DataSource.Factory manifestDataSourceFactory) {
this.chunkSourceFactory = Assertions.checkNotNull(chunkSourceFactory);
this.manifestDataSourceFactory = manifestDataSourceFactory;
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
livePresentationDelayMs = DEFAULT_LIVE_PRESENTATION_DELAY_MS;
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
streamKeys = Collections.emptyList();
}
/**
* @deprecated Use {@link MediaItem.Builder#setTag(Object)} and {@link
* #createMediaSource(MediaItem)} instead.
*/
@Deprecated
public Factory setTag(@Nullable Object tag) {
this.tag = tag;
return this;
}
/** @deprecated Use {@link #setLoadErrorHandlingPolicy(LoadErrorHandlingPolicy)} instead. */
@Deprecated
public Factory setMinLoadableRetryCount(int minLoadableRetryCount) {
return setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(minLoadableRetryCount));
}
/**
* Sets the {@link LoadErrorHandlingPolicy}. The default value is created by calling {@link
* DefaultLoadErrorHandlingPolicy#DefaultLoadErrorHandlingPolicy()}.
*
* <p>Calling this method overrides any calls to {@link #setMinLoadableRetryCount(int)}.
*
* @param loadErrorHandlingPolicy A {@link LoadErrorHandlingPolicy}.
* @return This factory, for convenience.
*/
public Factory setLoadErrorHandlingPolicy(
@Nullable LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
this.loadErrorHandlingPolicy =
loadErrorHandlingPolicy != null
? loadErrorHandlingPolicy
: new DefaultLoadErrorHandlingPolicy();
return this;
}
/**
* Sets the duration in milliseconds by which the default start position should precede the end
* of the live window for live playbacks. The default value is {@link
* #DEFAULT_LIVE_PRESENTATION_DELAY_MS}.
*
* @param livePresentationDelayMs For live playbacks, the duration in milliseconds by which the
* default start position should precede the end of the live window.
* @return This factory, for convenience.
*/
public Factory setLivePresentationDelayMs(long livePresentationDelayMs) {
this.livePresentationDelayMs = livePresentationDelayMs;
return this;
}
/**
* Sets the manifest parser to parse loaded manifest data when loading a manifest URI.
*
* @param manifestParser A parser for loaded manifest data.
* @return This factory, for convenience.
*/
public Factory setManifestParser(
@Nullable ParsingLoadable.Parser<? extends SsManifest> manifestParser) {
this.manifestParser = manifestParser;
return this;
}
/**
* Sets the factory to create composite {@link SequenceableLoader}s for when this media source
* loads data from multiple streams (video, audio etc.). The default is an instance of {@link
* DefaultCompositeSequenceableLoaderFactory}.
*
* @param compositeSequenceableLoaderFactory A factory to create composite {@link
* SequenceableLoader}s for when this media source loads data from multiple streams (video,
* audio etc.).
* @return This factory, for convenience.
*/
public Factory setCompositeSequenceableLoaderFactory(
@Nullable CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory) {
this.compositeSequenceableLoaderFactory =
compositeSequenceableLoaderFactory != null
? compositeSequenceableLoaderFactory
: new DefaultCompositeSequenceableLoaderFactory();
return this;
}
/**
* Sets the {@link DrmSessionManager} to use for acquiring {@link DrmSession DrmSessions}. The
* default value is {@link DrmSessionManager#DUMMY}.
*
* @param drmSessionManager The {@link DrmSessionManager}.
* @return This factory, for convenience.
*/
@Override
public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
this.drmSessionManager =
drmSessionManager != null
? drmSessionManager
: DrmSessionManager.getDummyDrmSessionManager();
return this;
}
/**
* @deprecated Use {@link MediaItem.Builder#setStreamKeys(List)} and {@link
* #createMediaSource(MediaItem)} instead.
*/
@SuppressWarnings("deprecation")
@Deprecated
@Override
public Factory setStreamKeys(@Nullable List<StreamKey> streamKeys) {
this.streamKeys = streamKeys != null ? streamKeys : Collections.emptyList();
return this;
}
/** @deprecated Use {@link #createMediaSource(MediaItem)} instead. */
@SuppressWarnings("deprecation")
@Deprecated
@Override
public SsMediaSource createMediaSource(Uri uri) {
return createMediaSource(new MediaItem.Builder().setSourceUri(uri).build());
}
/**
* Returns a new {@link SsMediaSource} using the current parameters and the specified sideloaded
* manifest.
*
* @param manifest The manifest. {@link SsManifest#isLive} must be false.
* @return The new {@link SsMediaSource}.
* @throws IllegalArgumentException If {@link SsManifest#isLive} is true.
*/
public SsMediaSource createMediaSource(SsManifest manifest) {
Assertions.checkArgument(!manifest.isLive);
if (!streamKeys.isEmpty()) {
manifest = manifest.copy(streamKeys);
}
return new SsMediaSource(
manifest,
/* manifestUri= */ null,
/* manifestDataSourceFactory= */ null,
/* manifestParser= */ null,
chunkSourceFactory,
compositeSequenceableLoaderFactory,
drmSessionManager,
loadErrorHandlingPolicy,
livePresentationDelayMs,
tag);
}
/**
* @deprecated Use {@link #createMediaSource(SsManifest)} and {@link #addEventListener(Handler,
* MediaSourceEventListener)} instead.
*/
@Deprecated
public SsMediaSource createMediaSource(
SsManifest manifest,
@Nullable Handler eventHandler,
@Nullable MediaSourceEventListener eventListener) {
SsMediaSource mediaSource = createMediaSource(manifest);
if (eventHandler != null && eventListener != null) {
mediaSource.addEventListener(eventHandler, eventListener);
}
return mediaSource;
}
/**
* @deprecated Use {@link #createMediaSource(Uri)} and {@link #addEventListener(Handler,
* MediaSourceEventListener)} instead.
*/
@Deprecated
public SsMediaSource createMediaSource(
Uri manifestUri,
@Nullable Handler eventHandler,
@Nullable MediaSourceEventListener eventListener) {
SsMediaSource mediaSource = createMediaSource(manifestUri);
if (eventHandler != null && eventListener != null) {
mediaSource.addEventListener(eventHandler, eventListener);
}
return mediaSource;
}
/**
* Returns a new {@link SsMediaSource} using the current parameters.
*
* @param mediaItem The {@link MediaItem}.
* @return The new {@link SsMediaSource}.
* @throws NullPointerException if {@link MediaItem#playbackProperties} is {@code null}.
*/
@Override
public SsMediaSource createMediaSource(MediaItem mediaItem) {
Assertions.checkNotNull(mediaItem.playbackProperties);
@Nullable ParsingLoadable.Parser<? extends SsManifest> manifestParser = this.manifestParser;
if (manifestParser == null) {
manifestParser = new SsManifestParser();
}
List<StreamKey> streamKeys =
!mediaItem.playbackProperties.streamKeys.isEmpty()
? mediaItem.playbackProperties.streamKeys
: this.streamKeys;
if (!streamKeys.isEmpty()) {
manifestParser = new FilteringManifestParser<>(manifestParser, streamKeys);
}
return new SsMediaSource(
/* manifest= */ null,
mediaItem.playbackProperties.sourceUri,
manifestDataSourceFactory,
manifestParser,
chunkSourceFactory,
compositeSequenceableLoaderFactory,
drmSessionManager,
loadErrorHandlingPolicy,
livePresentationDelayMs,
mediaItem.playbackProperties.tag != null ? mediaItem.playbackProperties.tag : tag);
}
@Override
public int[] getSupportedTypes() {
return new int[] {C.TYPE_SS};
}
}
/**
* The default presentation delay for live streams. The presentation delay is the duration by
* which the default start position precedes the end of the live window.
*/
public static final long DEFAULT_LIVE_PRESENTATION_DELAY_MS = 30000;
/**
* The minimum period between manifest refreshes.
*/
private static final int MINIMUM_MANIFEST_REFRESH_PERIOD_MS = 5000;
/**
* The minimum default start position for live streams, relative to the start of the live window.
*/
private static final long MIN_LIVE_DEFAULT_START_POSITION_US = 5000000;
private final boolean sideloadedManifest;
private final Uri manifestUri;
private final DataSource.Factory manifestDataSourceFactory;
private final SsChunkSource.Factory chunkSourceFactory;
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private final DrmSessionManager drmSessionManager;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final long livePresentationDelayMs;
private final EventDispatcher manifestEventDispatcher;
private final ParsingLoadable.Parser<? extends SsManifest> manifestParser;
private final ArrayList<SsMediaPeriod> mediaPeriods;
@Nullable private final Object tag;
private DataSource manifestDataSource;
private Loader manifestLoader;
private LoaderErrorThrower manifestLoaderErrorThrower;
@Nullable private TransferListener mediaTransferListener;
private long manifestLoadStartTimestamp;
private SsManifest manifest;
private Handler manifestRefreshHandler;
/**
* Constructs an instance to play a given {@link SsManifest}, which must not be live.
*
* @param manifest The manifest. {@link SsManifest#isLive} must be false.
* @param chunkSourceFactory A factory for {@link SsChunkSource} instances.
* @param eventHandler A handler for events. May be null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @deprecated Use {@link Factory} instead.
*/
@Deprecated
@SuppressWarnings("deprecation")
public SsMediaSource(
SsManifest manifest,
SsChunkSource.Factory chunkSourceFactory,
@Nullable Handler eventHandler,
@Nullable MediaSourceEventListener eventListener) {
this(
manifest,
chunkSourceFactory,
DefaultLoadErrorHandlingPolicy.DEFAULT_MIN_LOADABLE_RETRY_COUNT,
eventHandler,
eventListener);
}
/**
* Constructs an instance to play a given {@link SsManifest}, which must not be live.
*
* @param manifest The manifest. {@link SsManifest#isLive} must be false.
* @param chunkSourceFactory A factory for {@link SsChunkSource} instances.
* @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs.
* @param eventHandler A handler for events. May be null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @deprecated Use {@link Factory} instead.
*/
@Deprecated
public SsMediaSource(
SsManifest manifest,
SsChunkSource.Factory chunkSourceFactory,
int minLoadableRetryCount,
@Nullable Handler eventHandler,
@Nullable MediaSourceEventListener eventListener) {
this(
manifest,
/* manifestUri= */ null,
/* manifestDataSourceFactory= */ null,
/* manifestParser= */ null,
chunkSourceFactory,
new DefaultCompositeSequenceableLoaderFactory(),
DrmSessionManager.getDummyDrmSessionManager(),
new DefaultLoadErrorHandlingPolicy(minLoadableRetryCount),
DEFAULT_LIVE_PRESENTATION_DELAY_MS,
/* tag= */ null);
if (eventHandler != null && eventListener != null) {
addEventListener(eventHandler, eventListener);
}
}
/**
* Constructs an instance to play the manifest at a given {@link Uri}, which may be live or
* on-demand.
*
* @param manifestUri The manifest {@link Uri}.
* @param manifestDataSourceFactory A factory for {@link DataSource} instances that will be used
* to load (and refresh) the manifest.
* @param chunkSourceFactory A factory for {@link SsChunkSource} instances.
* @param eventHandler A handler for events. May be null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @deprecated Use {@link Factory} instead.
*/
@Deprecated
@SuppressWarnings("deprecation")
public SsMediaSource(
Uri manifestUri,
DataSource.Factory manifestDataSourceFactory,
SsChunkSource.Factory chunkSourceFactory,
@Nullable Handler eventHandler,
@Nullable MediaSourceEventListener eventListener) {
this(
manifestUri,
manifestDataSourceFactory,
chunkSourceFactory,
DefaultLoadErrorHandlingPolicy.DEFAULT_MIN_LOADABLE_RETRY_COUNT,
DEFAULT_LIVE_PRESENTATION_DELAY_MS,
eventHandler,
eventListener);
}
/**
* Constructs an instance to play the manifest at a given {@link Uri}, which may be live or
* on-demand.
*
* @param manifestUri The manifest {@link Uri}.
* @param manifestDataSourceFactory A factory for {@link DataSource} instances that will be used
* to load (and refresh) the manifest.
* @param chunkSourceFactory A factory for {@link SsChunkSource} instances.
* @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs.
* @param livePresentationDelayMs For live playbacks, the duration in milliseconds by which the
* default start position should precede the end of the live window.
* @param eventHandler A handler for events. May be null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @deprecated Use {@link Factory} instead.
*/
@Deprecated
@SuppressWarnings("deprecation")
public SsMediaSource(
Uri manifestUri,
DataSource.Factory manifestDataSourceFactory,
SsChunkSource.Factory chunkSourceFactory,
int minLoadableRetryCount,
long livePresentationDelayMs,
@Nullable Handler eventHandler,
@Nullable MediaSourceEventListener eventListener) {
this(manifestUri, manifestDataSourceFactory, new SsManifestParser(), chunkSourceFactory,
minLoadableRetryCount, livePresentationDelayMs, eventHandler, eventListener);
}
/**
* Constructs an instance to play the manifest at a given {@link Uri}, which may be live or
* on-demand.
*
* @param manifestUri The manifest {@link Uri}.
* @param manifestDataSourceFactory A factory for {@link DataSource} instances that will be used
* to load (and refresh) the manifest.
* @param manifestParser A parser for loaded manifest data.
* @param chunkSourceFactory A factory for {@link SsChunkSource} instances.
* @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs.
* @param livePresentationDelayMs For live playbacks, the duration in milliseconds by which the
* default start position should precede the end of the live window.
* @param eventHandler A handler for events. May be null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @deprecated Use {@link Factory} instead.
*/
@Deprecated
public SsMediaSource(
Uri manifestUri,
DataSource.Factory manifestDataSourceFactory,
ParsingLoadable.Parser<? extends SsManifest> manifestParser,
SsChunkSource.Factory chunkSourceFactory,
int minLoadableRetryCount,
long livePresentationDelayMs,
@Nullable Handler eventHandler,
@Nullable MediaSourceEventListener eventListener) {
this(
/* manifest= */ null,
manifestUri,
manifestDataSourceFactory,
manifestParser,
chunkSourceFactory,
new DefaultCompositeSequenceableLoaderFactory(),
DrmSessionManager.getDummyDrmSessionManager(),
new DefaultLoadErrorHandlingPolicy(minLoadableRetryCount),
livePresentationDelayMs,
/* tag= */ null);
if (eventHandler != null && eventListener != null) {
addEventListener(eventHandler, eventListener);
}
}
private SsMediaSource(
@Nullable SsManifest manifest,
@Nullable Uri manifestUri,
@Nullable DataSource.Factory manifestDataSourceFactory,
@Nullable ParsingLoadable.Parser<? extends SsManifest> manifestParser,
SsChunkSource.Factory chunkSourceFactory,
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
DrmSessionManager drmSessionManager,
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
long livePresentationDelayMs,
@Nullable Object tag) {
Assertions.checkState(manifest == null || !manifest.isLive);
this.manifest = manifest;
this.manifestUri = manifestUri == null ? null : SsUtil.fixManifestUri(manifestUri);
this.manifestDataSourceFactory = manifestDataSourceFactory;
this.manifestParser = manifestParser;
this.chunkSourceFactory = chunkSourceFactory;
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
this.drmSessionManager = drmSessionManager;
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
this.livePresentationDelayMs = livePresentationDelayMs;
this.manifestEventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null);
this.tag = tag;
sideloadedManifest = manifest != null;
mediaPeriods = new ArrayList<>();
}
// MediaSource implementation.
@Override
@Nullable
public Object getTag() {
return tag;
}
@Override
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
this.mediaTransferListener = mediaTransferListener;
drmSessionManager.prepare();
if (sideloadedManifest) {
manifestLoaderErrorThrower = new LoaderErrorThrower.Dummy();
processManifest();
} else {
manifestDataSource = manifestDataSourceFactory.createDataSource();
manifestLoader = new Loader("Loader:Manifest");
manifestLoaderErrorThrower = manifestLoader;
manifestRefreshHandler = Util.createHandler();
startLoadingManifest();
}
}
@Override
public void maybeThrowSourceInfoRefreshError() throws IOException {
manifestLoaderErrorThrower.maybeThrowError();
}
@Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
EventDispatcher eventDispatcher = createEventDispatcher(id);
SsMediaPeriod period =
new SsMediaPeriod(
manifest,
chunkSourceFactory,
mediaTransferListener,
compositeSequenceableLoaderFactory,
drmSessionManager,
loadErrorHandlingPolicy,
eventDispatcher,
manifestLoaderErrorThrower,
allocator);
mediaPeriods.add(period);
return period;
}
@Override
public void releasePeriod(MediaPeriod period) {
((SsMediaPeriod) period).release();
mediaPeriods.remove(period);
}
@Override
protected void releaseSourceInternal() {
manifest = sideloadedManifest ? manifest : null;
manifestDataSource = null;
manifestLoadStartTimestamp = 0;
if (manifestLoader != null) {
manifestLoader.release();
manifestLoader = null;
}
if (manifestRefreshHandler != null) {
manifestRefreshHandler.removeCallbacksAndMessages(null);
manifestRefreshHandler = null;
}
drmSessionManager.release();
}
// Loader.Callback implementation
@Override
public void onLoadCompleted(
ParsingLoadable<SsManifest> loadable, long elapsedRealtimeMs, long loadDurationMs) {
manifestEventDispatcher.loadCompleted(
new LoadEventInfo(
loadable.dataSpec,
loadable.getUri(),
loadable.getResponseHeaders(),
elapsedRealtimeMs,
loadDurationMs,
loadable.bytesLoaded()),
loadable.type);
manifest = loadable.getResult();
manifestLoadStartTimestamp = elapsedRealtimeMs - loadDurationMs;
processManifest();
scheduleManifestRefresh();
}
@Override
public void onLoadCanceled(
ParsingLoadable<SsManifest> loadable,
long elapsedRealtimeMs,
long loadDurationMs,
boolean released) {
manifestEventDispatcher.loadCanceled(
new LoadEventInfo(
loadable.dataSpec,
loadable.getUri(),
loadable.getResponseHeaders(),
elapsedRealtimeMs,
loadDurationMs,
loadable.bytesLoaded()),
loadable.type);
}
@Override
public LoadErrorAction onLoadError(
ParsingLoadable<SsManifest> loadable,
long elapsedRealtimeMs,
long loadDurationMs,
IOException error,
int errorCount) {
long retryDelayMs =
loadErrorHandlingPolicy.getRetryDelayMsFor(
C.DATA_TYPE_MANIFEST, loadDurationMs, error, errorCount);
LoadErrorAction loadErrorAction =
retryDelayMs == C.TIME_UNSET
? Loader.DONT_RETRY_FATAL
: Loader.createRetryAction(/* resetErrorCount= */ false, retryDelayMs);
manifestEventDispatcher.loadError(
new LoadEventInfo(
loadable.dataSpec,
loadable.getUri(),
loadable.getResponseHeaders(),
elapsedRealtimeMs,
loadDurationMs,
loadable.bytesLoaded()),
loadable.type,
error,
!loadErrorAction.isRetry());
return loadErrorAction;
}
// Internal methods
private void processManifest() {
for (int i = 0; i < mediaPeriods.size(); i++) {
mediaPeriods.get(i).updateManifest(manifest);
}
long startTimeUs = Long.MAX_VALUE;
long endTimeUs = Long.MIN_VALUE;
for (StreamElement element : manifest.streamElements) {
if (element.chunkCount > 0) {
startTimeUs = Math.min(startTimeUs, element.getStartTimeUs(0));
endTimeUs = Math.max(endTimeUs, element.getStartTimeUs(element.chunkCount - 1)
+ element.getChunkDurationUs(element.chunkCount - 1));
}
}
Timeline timeline;
if (startTimeUs == Long.MAX_VALUE) {
long periodDurationUs = manifest.isLive ? C.TIME_UNSET : 0;
timeline =
new SinglePeriodTimeline(
periodDurationUs,
/* windowDurationUs= */ 0,
/* windowPositionInPeriodUs= */ 0,
/* windowDefaultStartPositionUs= */ 0,
/* isSeekable= */ true,
/* isDynamic= */ manifest.isLive,
/* isLive= */ manifest.isLive,
manifest,
tag);
} else if (manifest.isLive) {
if (manifest.dvrWindowLengthUs != C.TIME_UNSET && manifest.dvrWindowLengthUs > 0) {
startTimeUs = Math.max(startTimeUs, endTimeUs - manifest.dvrWindowLengthUs);
}
long durationUs = endTimeUs - startTimeUs;
long defaultStartPositionUs = durationUs - C.msToUs(livePresentationDelayMs);
if (defaultStartPositionUs < MIN_LIVE_DEFAULT_START_POSITION_US) {
// The default start position is too close to the start of the live window. Set it to the
// minimum default start position provided the window is at least twice as big. Else set
// it to the middle of the window.
defaultStartPositionUs = Math.min(MIN_LIVE_DEFAULT_START_POSITION_US, durationUs / 2);
}
timeline =
new SinglePeriodTimeline(
/* periodDurationUs= */ C.TIME_UNSET,
durationUs,
startTimeUs,
defaultStartPositionUs,
/* isSeekable= */ true,
/* isDynamic= */ true,
/* isLive= */ true,
manifest,
tag);
} else {
long durationUs = manifest.durationUs != C.TIME_UNSET ? manifest.durationUs
: endTimeUs - startTimeUs;
timeline =
new SinglePeriodTimeline(
startTimeUs + durationUs,
durationUs,
startTimeUs,
/* windowDefaultStartPositionUs= */ 0,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* isLive= */ false,
manifest,
tag);
}
refreshSourceInfo(timeline);
}
private void scheduleManifestRefresh() {
if (!manifest.isLive) {
return;
}
long nextLoadTimestamp = manifestLoadStartTimestamp + MINIMUM_MANIFEST_REFRESH_PERIOD_MS;
long delayUntilNextLoad = Math.max(0, nextLoadTimestamp - SystemClock.elapsedRealtime());
manifestRefreshHandler.postDelayed(this::startLoadingManifest, delayUntilNextLoad);
}
private void startLoadingManifest() {
if (manifestLoader.hasFatalError()) {
return;
}
ParsingLoadable<SsManifest> loadable = new ParsingLoadable<>(manifestDataSource,
manifestUri, C.DATA_TYPE_MANIFEST, manifestParser);
long elapsedRealtimeMs =
manifestLoader.startLoading(
loadable, this, loadErrorHandlingPolicy.getMinimumLoadableRetryCount(loadable.type));
manifestEventDispatcher.loadStarted(
new LoadEventInfo(loadable.dataSpec, elapsedRealtimeMs), loadable.type);
}
}