diff --git a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaSource.java b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaSource.java index 4936899d9d..5ebcc34690 100644 --- a/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaSource.java +++ b/libraries/exoplayer_dash/src/main/java/androidx/media3/exoplayer/dash/DashMediaSource.java @@ -110,6 +110,7 @@ public final class DashMediaSource extends BaseMediaSource { private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private LoadErrorHandlingPolicy loadErrorHandlingPolicy; private long fallbackTargetLiveOffsetMs; + private long minLiveStartPositionUs; @Nullable private ParsingLoadable.Parser extends DashManifest> manifestParser; /** @@ -145,7 +146,7 @@ public final class DashMediaSource extends BaseMediaSource { * @param chunkSourceFactory A factory for {@link DashChunkSource} 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 + * used to create media sources with sideloaded manifests via {@link * #createMediaSource(DashManifest, MediaItem)}. */ public Factory( @@ -156,6 +157,7 @@ public final class DashMediaSource extends BaseMediaSource { drmSessionManagerProvider = new DefaultDrmSessionManagerProvider(); loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy(); fallbackTargetLiveOffsetMs = DEFAULT_FALLBACK_TARGET_LIVE_OFFSET_MS; + minLiveStartPositionUs = MIN_LIVE_DEFAULT_START_POSITION_US; compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory(); } @@ -199,6 +201,25 @@ public final class DashMediaSource extends BaseMediaSource { return this; } + /** + * Sets the minimum position to start playback from in a live stream, in microseconds relative + * to the start of the live window. + * + *
This value will override any suggested value from the manifest and helps to prevent {@link + * androidx.media3.exoplayer.source.BehindLiveWindowException} issues. + * + *
The default value is {@link #MIN_LIVE_DEFAULT_START_POSITION_US}. + * + * @param minLiveStartPositionUs The minimum live start position, in microseconds relative to + * the start of the live window. + * @return This factory, for convenience. + */ + @CanIgnoreReturnValue + public Factory setMinLiveStartPositionUs(long minLiveStartPositionUs) { + this.minLiveStartPositionUs = minLiveStartPositionUs; + return this; + } + /** * Sets the manifest parser to parse loaded manifest data when loading a manifest URI. * @@ -278,7 +299,8 @@ public final class DashMediaSource extends BaseMediaSource { compositeSequenceableLoaderFactory, drmSessionManagerProvider.get(mediaItem), loadErrorHandlingPolicy, - fallbackTargetLiveOffsetMs); + fallbackTargetLiveOffsetMs, + minLiveStartPositionUs); } /** @@ -309,7 +331,8 @@ public final class DashMediaSource extends BaseMediaSource { compositeSequenceableLoaderFactory, drmSessionManagerProvider.get(mediaItem), loadErrorHandlingPolicy, - fallbackTargetLiveOffsetMs); + fallbackTargetLiveOffsetMs, + minLiveStartPositionUs); } @Override @@ -330,16 +353,18 @@ public final class DashMediaSource extends BaseMediaSource { /** The media id used by media items of dash media sources without a manifest URI. */ public static final String DEFAULT_MEDIA_ID = "DashMediaSource"; + /** + * The minimum default start position for live streams, in microseconds relative to the start of + * the live window. + */ + public static final long MIN_LIVE_DEFAULT_START_POSITION_US = 5_000_000; + /** * The interval in milliseconds between invocations of {@link * MediaSourceCaller#onSourceInfoRefreshed(MediaSource, Timeline)} when the source's {@link * Timeline} is changing dynamically (for example, for incomplete live streams). */ private static final long DEFAULT_NOTIFY_MANIFEST_INTERVAL_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 = 5_000_000; private static final String TAG = "DashMediaSource"; @@ -352,6 +377,7 @@ public final class DashMediaSource extends BaseMediaSource { private final LoadErrorHandlingPolicy loadErrorHandlingPolicy; private final BaseUrlExclusionList baseUrlExclusionList; private final long fallbackTargetLiveOffsetMs; + private final long minLiveStartPositionUs; private final EventDispatcher manifestEventDispatcher; private final ParsingLoadable.Parser extends DashManifest> manifestParser; private final ManifestCallback manifestCallback; @@ -392,7 +418,8 @@ public final class DashMediaSource extends BaseMediaSource { CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, DrmSessionManager drmSessionManager, LoadErrorHandlingPolicy loadErrorHandlingPolicy, - long fallbackTargetLiveOffsetMs) { + long fallbackTargetLiveOffsetMs, + long minLiveStartPositionUs) { this.mediaItem = mediaItem; this.liveConfiguration = mediaItem.liveConfiguration; this.manifestUri = checkNotNull(mediaItem.localConfiguration).uri; @@ -404,6 +431,7 @@ public final class DashMediaSource extends BaseMediaSource { this.drmSessionManager = drmSessionManager; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.fallbackTargetLiveOffsetMs = fallbackTargetLiveOffsetMs; + this.minLiveStartPositionUs = minLiveStartPositionUs; this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; baseUrlExclusionList = new BaseUrlExclusionList(); sideloadedManifest = manifest != null; @@ -829,8 +857,7 @@ public final class DashMediaSource extends BaseMediaSource { windowStartUnixTimeMs = manifest.availabilityStartTimeMs + Util.usToMs(windowStartTimeInManifestUs); windowDefaultPositionUs = nowInWindowUs - Util.msToUs(liveConfiguration.targetOffsetMs); - long minimumWindowDefaultPositionUs = - min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2); + long minimumWindowDefaultPositionUs = min(minLiveStartPositionUs, windowDurationUs / 2); if (windowDefaultPositionUs < minimumWindowDefaultPositionUs) { // The default position is too close to the start of the live window. Set it to the minimum // default position provided the window is at least twice as big. Else set it to the middle @@ -939,8 +966,7 @@ public final class DashMediaSource extends BaseMediaSource { targetOffsetMs = minLiveOffsetMs; } if (targetOffsetMs > maxLiveOffsetMs) { - long safeDistanceFromWindowStartUs = - min(MIN_LIVE_DEFAULT_START_POSITION_US, windowDurationUs / 2); + long safeDistanceFromWindowStartUs = min(minLiveStartPositionUs, windowDurationUs / 2); long maxTargetOffsetForSafeDistanceToWindowStartMs = usToMs(nowInWindowUs - safeDistanceFromWindowStartUs); targetOffsetMs =