diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 4149407714..129037143b 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -445,7 +445,7 @@ import java.io.IOException; long playingPeriodDuration = timeline.getPeriodDurationUs(playingPeriod.index); if (allRenderersEnded && (playingPeriodDuration == C.UNSET_TIME_US - || playingPeriodDuration <= playbackInfo.positionUs) + || playingPeriodDuration <= playbackInfo.positionUs) && isTimelineEnded) { setState(ExoPlayer.STATE_ENDED); stopRenderers(); @@ -495,19 +495,17 @@ import java.io.IOException; private void seekToInternal(int periodIndex, long positionUs) throws ExoPlaybackException { try { - if (positionUs == C.UNSET_TIME_US && mediaSource != null) { - MediaSource.Position defaultStartPosition = - mediaSource.getDefaultStartPosition(periodIndex); - if (defaultStartPosition != null) { - // We know the default position so seek to it now. - periodIndex = defaultStartPosition.periodIndex; - positionUs = defaultStartPosition.positionUs; - } + if (positionUs == C.UNSET_TIME_US && timeline != null + && periodIndex < timeline.getPeriodCount()) { + // We know about the window, so seek to its default initial position now. + Window window = timeline.getPeriodWindow(periodIndex); + periodIndex = window.defaultInitialPeriodIndex; + positionUs = window.defaultInitialTimeMs * 1000; } if (periodIndex == playbackInfo.periodIndex && ((positionUs == C.UNSET_TIME_US && playbackInfo.positionUs == C.UNSET_TIME_US) - || ((positionUs / 1000) == (playbackInfo.positionUs / 1000)))) { + || ((positionUs / 1000) == (playbackInfo.positionUs / 1000)))) { // Seek position equals the current position. Do nothing. return; } @@ -805,22 +803,18 @@ import java.io.IOException; return; } - // Release all loaded periods and seek to the new playing period index. + // Release all loaded periods. releasePeriodsFrom(playingPeriod); playingPeriod = null; readingPeriod = null; loadingPeriod = null; - MediaSource.Position defaultStartPosition = - mediaSource.getDefaultStartPosition(newPlayingPeriodIndex); - long newPlayingPositionUs; - if (defaultStartPosition != null) { - newPlayingPeriodIndex = defaultStartPosition.periodIndex; - newPlayingPositionUs = seekToPeriodPosition(defaultStartPosition.periodIndex, - defaultStartPosition.positionUs); - } else { - newPlayingPositionUs = seekToPeriodPosition(newPlayingPeriodIndex, C.UNSET_TIME_US); - } + // Find the default initial position in the window and seek to it. + Window window = timeline.getPeriodWindow(newPlayingPeriodIndex); + newPlayingPeriodIndex = window.defaultInitialPeriodIndex; + long newPlayingPositionUs = seekToPeriodPosition(newPlayingPeriodIndex, + window.defaultInitialTimeMs); + playbackInfo = new PlaybackInfo(newPlayingPeriodIndex, newPlayingPositionUs); eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget(); return; @@ -901,33 +895,29 @@ import java.io.IOException; return; } - // Update the loading period. + if (loadingPeriod == null || (loadingPeriod.isFullyBuffered() && !loadingPeriod.isLast && bufferAheadPeriodCount < MAXIMUM_BUFFER_AHEAD_PERIODS)) { - int periodIndex = loadingPeriod == null ? playbackInfo.periodIndex : loadingPeriod.index + 1; - long startPositionUs = playbackInfo.positionUs; - if (loadingPeriod != null || startPositionUs == C.UNSET_TIME_US) { - // We are starting to load the next period or seeking to the default position, so request a - // period and position from the source. - MediaSource.Position defaultStartPosition = - mediaSource.getDefaultStartPosition(periodIndex); - if (defaultStartPosition != null) { - periodIndex = defaultStartPosition.periodIndex; - startPositionUs = defaultStartPosition.positionUs; - } else { - startPositionUs = C.UNSET_TIME_US; - } - } - - if (periodIndex >= timeline.getPeriodCount()) { - // This period is not available yet. + // We don't have a loading period or it's fully loaded, so try and create the next one. + int newLoadingPeriodIndex = loadingPeriod == null ? playbackInfo.periodIndex + : loadingPeriod.index + 1; + if (newLoadingPeriodIndex >= timeline.getPeriodCount()) { + // The period is not available yet. mediaSource.maybeThrowSourceInfoRefreshError(); - } else if (startPositionUs != C.UNSET_TIME_US) { - MediaPeriod mediaPeriod = mediaSource.createPeriod(periodIndex, this, + } else { + Window window = timeline.getPeriodWindow(newLoadingPeriodIndex); + long startPositionUs = loadingPeriod == null ? playbackInfo.positionUs : C.UNSET_TIME_US; + if (startPositionUs == C.UNSET_TIME_US) { + // This is the first period of a new window or we don't have a start position, so seek to + // the default position for the window. + newLoadingPeriodIndex = window.defaultInitialPeriodIndex; + startPositionUs = window.defaultInitialTimeMs * 1000; + } + MediaPeriod mediaPeriod = mediaSource.createPeriod(newLoadingPeriodIndex, this, loadControl.getAllocator(), startPositionUs); Period newPeriod = new Period(renderers, rendererCapabilities, trackSelector, mediaSource, - mediaPeriod, timeline.getPeriodId(periodIndex), startPositionUs); - newPeriod.setIndex(timeline, periodIndex); + mediaPeriod, timeline.getPeriodId(newLoadingPeriodIndex), startPositionUs); + newPeriod.setIndex(timeline, newLoadingPeriodIndex); if (loadingPeriod != null) { loadingPeriod.setNextPeriod(newPeriod); newPeriod.offsetUs = loadingPeriod.offsetUs diff --git a/library/src/main/java/com/google/android/exoplayer2/Window.java b/library/src/main/java/com/google/android/exoplayer2/Window.java index bc08e83b99..bc4da56da9 100644 --- a/library/src/main/java/com/google/android/exoplayer2/Window.java +++ b/library/src/main/java/com/google/android/exoplayer2/Window.java @@ -21,8 +21,8 @@ package com.google.android.exoplayer2; public final class Window { /** - * Creates a new {@link Window} containing times from zero up to {@code durationUs} in the first - * period. + * Creates a new {@link Window} consisting of a single period starting at time zero and with the + * specified duration. The default initial position is the start of the window. * * @param durationUs The duration of the window, in microseconds. * @param isSeekable Whether seeking is supported within the window. @@ -30,7 +30,27 @@ public final class Window { */ public static Window createWindowFromZero(long durationUs, boolean isSeekable, boolean isDynamic) { - return createWindow(0, 0, 0, durationUs, durationUs, isSeekable, isDynamic); + return createWindow(0, 0, 0, durationUs, durationUs, isSeekable, isDynamic, 0, 0); + } + + /** + * Creates a new {@link Window} representing the specified time range. The default initial + * position is the start of the window. + * + * @param startPeriodIndex The index of the period containing the start of the window. + * @param startTimeUs The start time of the window in microseconds, relative to the start of the + * specified start period. + * @param endPeriodIndex The index of the period containing the end of the window. + * @param endTimeUs The end time of the window in microseconds, relative to the start of the + * specified end period. + * @param durationUs The duration of the window in microseconds. + * @param isSeekable Whether seeking is supported within the window. + * @param isDynamic Whether this seek window may change when the timeline is updated. + */ + public static Window createWindow(int startPeriodIndex, long startTimeUs, + int endPeriodIndex, long endTimeUs, long durationUs, boolean isSeekable, boolean isDynamic) { + return createWindow(startPeriodIndex, startTimeUs, endPeriodIndex, endTimeUs, durationUs, + isSeekable, isDynamic, startPeriodIndex, startTimeUs); } /** @@ -45,13 +65,18 @@ public final class Window { * @param durationUs The duration of the window in microseconds. * @param isSeekable Whether seeking is supported within the window. * @param isDynamic Whether this seek window may change when the timeline is updated. + * @param defaultInitialPeriodIndex The index of the period containing the default position from + * which playback should start. + * @param defaultInitialTimeUs The time of the default position from which playback should start + * in microseconds, relative to the start of the period that contains it. */ public static Window createWindow(int startPeriodIndex, long startTimeUs, - int endPeriodIndex, long endTimeUs, long durationUs, boolean isSeekable, boolean isDynamic) { + int endPeriodIndex, long endTimeUs, long durationUs, boolean isSeekable, boolean isDynamic, + int defaultInitialPeriodIndex, long defaultInitialTimeUs) { return new Window(startPeriodIndex, startTimeUs / 1000, endPeriodIndex, endTimeUs == C.UNSET_TIME_US ? ExoPlayer.UNKNOWN_TIME : (endTimeUs / 1000), durationUs == C.UNSET_TIME_US ? ExoPlayer.UNKNOWN_TIME : (durationUs / 1000), - isSeekable, isDynamic); + isSeekable, isDynamic, defaultInitialPeriodIndex, defaultInitialTimeUs / 1000); } /** @@ -84,9 +109,19 @@ public final class Window { * Whether this seek window may change when the timeline is updated. */ public final boolean isDynamic; + /** + * The period index of the default position from which playback should start. + */ + public final int defaultInitialPeriodIndex; + /** + * The time of the default position relative to the start of the period at + * {@link #defaultInitialPeriodIndex}, in milliseconds. + */ + public final long defaultInitialTimeMs; private Window(int startPeriodIndex, long startTimeMs, int endPeriodIndex, long endTimeMs, - long durationMs, boolean isSeekable, boolean isDynamic) { + long durationMs, boolean isSeekable, boolean isDynamic, int defaultInitialPeriodIndex, + long defaultInitialTimeMs) { this.startPeriodIndex = startPeriodIndex; this.startTimeMs = startTimeMs; this.endPeriodIndex = endPeriodIndex; @@ -94,6 +129,8 @@ public final class Window { this.durationMs = durationMs; this.isSeekable = isSeekable; this.isDynamic = isDynamic; + this.defaultInitialPeriodIndex = defaultInitialPeriodIndex; + this.defaultInitialTimeMs = defaultInitialTimeMs; } /** @@ -105,7 +142,8 @@ public final class Window { */ public Window copyOffsetByPeriodCount(int periodCount) { return new Window(startPeriodIndex + periodCount, startTimeMs, endPeriodIndex + periodCount, - endTimeMs, durationMs, isSeekable, isDynamic); + endTimeMs, durationMs, isSeekable, isDynamic, defaultInitialPeriodIndex + periodCount, + defaultInitialTimeMs); } @Override @@ -115,7 +153,10 @@ public final class Window { result = 31 * result + (int) startTimeMs; result = 31 * result + endPeriodIndex; result = 31 * result + (int) endTimeMs; + result = 31 * result + (isSeekable ? 1 : 2); result = 31 * result + (isDynamic ? 1 : 2); + result = 31 * result + defaultInitialPeriodIndex; + result = 31 * result + (int) defaultInitialTimeMs; return result; } @@ -134,7 +175,9 @@ public final class Window { && other.endTimeMs == endTimeMs && other.durationMs == durationMs && other.isSeekable == isSeekable - && other.isDynamic == isDynamic; + && other.isDynamic == isDynamic + && other.defaultInitialPeriodIndex == defaultInitialPeriodIndex + && other.defaultInitialTimeMs == defaultInitialTimeMs; } @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index a08cdbf292..3c4cb95c61 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -82,16 +82,6 @@ public final class ConcatenatingMediaSource implements MediaSource { oldPlayingPeriodIndex - oldFirstPeriodIndex, oldTimeline.timelines[sourceIndex]); } - @Override - public Position getDefaultStartPosition(int index) { - int sourceIndex = timeline.getSourceIndexForPeriod(index); - int sourceFirstPeriodIndex = timeline.getFirstPeriodIndexInSource(sourceIndex); - Position defaultStartPosition = - mediaSources[sourceIndex].getDefaultStartPosition(index - sourceFirstPeriodIndex); - return new Position(defaultStartPosition.periodIndex + sourceFirstPeriodIndex, - defaultStartPosition.positionUs); - } - @Override public void maybeThrowSourceInfoRefreshError() throws IOException { for (MediaSource mediaSource : mediaSources) { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java index bd77dae86d..5f5df71333 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java @@ -144,11 +144,6 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List return 0; } - @Override - public Position getDefaultStartPosition(int index) { - return Position.DEFAULT; - } - @Override public void maybeThrowSourceInfoRefreshError() throws IOException { // Do nothing. diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java index f06809bbb9..f15e9b782f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/MediaSource.java @@ -40,39 +40,6 @@ public interface MediaSource { } - /** - * A position in the timeline. - */ - final class Position { - - /** - * A start position at the earliest time in the first period. - */ - public static final Position DEFAULT = new Position(0, 0); - - /** - * The index of the period containing the timeline position. - */ - public final int periodIndex; - - /** - * The position in microseconds within the period. - */ - public final long positionUs; - - /** - * Creates a new timeline position. - * - * @param periodIndex The index of the period containing the timeline position. - * @param positionUs The position in microseconds within the period. - */ - public Position(int periodIndex, long positionUs) { - this.periodIndex = periodIndex; - this.positionUs = positionUs; - } - - } - /** * Starts preparation of the source. * @@ -93,20 +60,6 @@ public interface MediaSource { */ int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline); - /** - * Returns the default {@link Position} that the player should play when when starting to play the - * period at {@code index}, or {@code null} if the default position is not yet known. - *
- * For example, sources can return a {@link Position} with the passed period {@code index} to play - * the period at {@code index} immediately after the period at {@code index - 1}. Sources - * providing multi-period live streams may return the index and position of the live edge when - * passed {@code index == 0} to play from the live edge. - * - * @param index The index of the requested period. - * @return The default start position. - */ - Position getDefaultStartPosition(int index); - /** * Throws any pending error encountered while loading or refreshing source information. */ diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java index 31a5e090d2..1d06dab5f0 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java @@ -70,11 +70,6 @@ public final class MergingMediaSource implements MediaSource { return mediaSources[0].getNewPlayingPeriodIndex(oldPlayingPeriodIndex, oldTimeline); } - @Override - public Position getDefaultStartPosition(int index) { - return mediaSources[0].getDefaultStartPosition(index); - } - @Override public void maybeThrowSourceInfoRefreshError() throws IOException { for (MediaSource mediaSource : mediaSources) { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java index 876a89d05b..c5e1d72b98 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java @@ -94,11 +94,6 @@ public final class SingleSampleMediaSource implements MediaSource { return oldPlayingPeriodIndex; } - @Override - public Position getDefaultStartPosition(int index) { - return Position.DEFAULT; - } - @Override public void maybeThrowSourceInfoRefreshError() throws IOException { // Do nothing. diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index a52795c24a..9b01d45fda 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -59,14 +59,14 @@ public final class DashMediaSource implements MediaSource { public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; /** * A constant indicating that the live edge offset (the offset subtracted from the live edge - * when calculating the default position returned by {@link #getDefaultStartPosition(int)}) should - * be set to {@link DashManifest#suggestedPresentationDelay} if specified by the manifest, or + * when calculating the default initial playback position) should be set to + * {@link DashManifest#suggestedPresentationDelay} if specified by the manifest, or * {@link #DEFAULT_LIVE_EDGE_OFFSET_FIXED_MS} otherwise. */ public static final long DEFAULT_LIVE_EDGE_OFFSET_PREFER_MANIFEST_MS = -1; /** * A fixed default live edge offset (the offset subtracted from the live edge when calculating the - * default position returned by {@link #getDefaultStartPosition(int)}). + * default initial playback position). */ public static final long DEFAULT_LIVE_EDGE_OFFSET_FIXED_MS = 30000; /** @@ -167,32 +167,6 @@ public final class DashMediaSource implements MediaSource { return 0; } - @Override - public Position getDefaultStartPosition(int index) { - if (window == null) { - return null; - } - - if (index == 0 && manifest.dynamic) { - // The stream is live, so return a position a position offset from the live edge. - int periodIndex = window.endPeriodIndex; - long liveEdgeOffsetForManifest = liveEdgeOffsetMs; - if (liveEdgeOffsetForManifest == DEFAULT_LIVE_EDGE_OFFSET_PREFER_MANIFEST_MS) { - liveEdgeOffsetForManifest = manifest.suggestedPresentationDelay != -1 - ? manifest.suggestedPresentationDelay : DEFAULT_LIVE_EDGE_OFFSET_FIXED_MS; - } - long positionMs = window.endTimeMs - liveEdgeOffsetForManifest; - while (positionMs < 0 && periodIndex > window.startPeriodIndex) { - periodIndex--; - positionMs += manifest.getPeriodDurationMs(periodIndex); - } - positionMs = Math.max(positionMs, - periodIndex == window.startPeriodIndex ? window.startTimeMs : 0); - return new Position(periodIndex, positionMs * 1000); - } - return new Position(index, 0); - } - @Override public void maybeThrowSourceInfoRefreshError() throws IOException { loader.maybeThrowError(); @@ -405,8 +379,27 @@ public final class DashMediaSource implements MediaSource { for (int i = 0; i < manifest.getPeriodCount() - 1; i++) { windowDurationUs += manifest.getPeriodDurationUs(i); } + int defaultInitialPeriodIndex = 0; + long defaultInitialTimeUs = 0; + if (manifest.dynamic) { + defaultInitialPeriodIndex = lastPeriodIndex; + long liveEdgeOffsetForManifestMs = liveEdgeOffsetMs; + if (liveEdgeOffsetForManifestMs == DEFAULT_LIVE_EDGE_OFFSET_PREFER_MANIFEST_MS) { + liveEdgeOffsetForManifestMs = manifest.suggestedPresentationDelay != -1 + ? manifest.suggestedPresentationDelay : DEFAULT_LIVE_EDGE_OFFSET_FIXED_MS; + } + defaultInitialTimeUs = currentEndTimeUs - (liveEdgeOffsetForManifestMs * 1000); + while (defaultInitialTimeUs < 0 && defaultInitialPeriodIndex > 0) { + defaultInitialPeriodIndex--; + defaultInitialTimeUs += manifest.getPeriodDurationUs(defaultInitialPeriodIndex); + } + if (defaultInitialPeriodIndex == 0) { + defaultInitialTimeUs = Math.max(defaultInitialTimeUs, currentStartTimeUs); + } + } window = Window.createWindow(0, currentStartTimeUs, lastPeriodIndex, currentEndTimeUs, - windowDurationUs, true /* isSeekable */, manifest.dynamic); + windowDurationUs, true /* isSeekable */, manifest.dynamic, defaultInitialPeriodIndex, + defaultInitialTimeUs); sourceListener.onSourceInfoRefreshed(new DashTimeline(firstPeriodId, manifest, window), manifest); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index 086ea61689..fcc8093ef1 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -73,12 +73,6 @@ public final class HlsMediaSource implements MediaSource { return oldPlayingPeriodIndex; } - @Override - public Position getDefaultStartPosition(int index) { - // TODO: Return the position of the live edge, if applicable. - return Position.DEFAULT; - } - @Override public void maybeThrowSourceInfoRefreshError() { // Do nothing. diff --git a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index 1fc9469f94..5730e05980 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -52,7 +52,7 @@ public final class SsMediaSource implements MediaSource, public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; /** * A default live edge offset (the offset subtracted from the live edge when calculating the - * default position returned by {@link #getDefaultStartPosition(int)}). + * default initial playback position. */ public static final long DEFAULT_LIVE_EDGE_OFFSET_MS = 30000; @@ -73,7 +73,6 @@ public final class SsMediaSource implements MediaSource, private long manifestLoadStartTimestamp; private SsManifest manifest; - private Window window; private Handler manifestRefreshHandler; @@ -114,19 +113,6 @@ public final class SsMediaSource implements MediaSource, return oldPlayingPeriodIndex; } - @Override - public Position getDefaultStartPosition(int index) { - if (window == null) { - return null; - } - if (manifest.isLive) { - long startPositionUs = Math.max(window.startTimeMs, - window.endTimeMs - liveEdgeOffsetMs) * 1000; - return new Position(0, startPositionUs); - } - return Position.DEFAULT; - } - @Override public void maybeThrowSourceInfoRefreshError() throws IOException { manifestLoader.maybeThrowError(); @@ -196,15 +182,16 @@ public final class SsMediaSource implements MediaSource, startTimeUs = Math.max(startTimeUs, endTimeUs - manifest.dvrWindowLengthUs); } long durationUs = endTimeUs - startTimeUs; + long defaultInitialStartPositionUs = Math.max(startTimeUs, + endTimeUs - (liveEdgeOffsetMs * 1000)); Window window = Window.createWindow(0, startTimeUs, 0, endTimeUs, durationUs, - true /* isSeekable */, true /* isDynamic */); + true /* isSeekable */, true /* isDynamic */, 0, defaultInitialStartPositionUs); timeline = new SinglePeriodTimeline(endTimeUs, window); } } else { boolean isSeekable = manifest.durationUs != C.UNSET_TIME_US; timeline = new SinglePeriodTimeline(manifest.durationUs, isSeekable); } - window = timeline.getWindow(0); sourceListener.onSourceInfoRefreshed(timeline, manifest); scheduleManifestRefresh(); }