Suppress no-op timeline changes in ExtractorMediaSource

When an ExtractorMediaSource is used in a concatenation, and
probably when using repeat modes, it needs to produce multiple
ExtractorMediaPeriods during usage. Currently we fire a
source info refresh every time a new ExtractorMediaPeriod
instance prepares, which triggers ExoPlayer.EventListener's
onTimelineChanged method. In nearly all cases the timeline is
unchanged after the first ExtractorMediaPeriod is prepared.
This change suppresses these no-op changes.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=162484234
This commit is contained in:
olly 2017-07-19 07:43:43 -07:00 committed by Oliver Woodman
parent e5952d4859
commit 108f4f14f3
2 changed files with 42 additions and 22 deletions

View File

@ -50,6 +50,21 @@ import java.util.Arrays;
Loader.Callback<ExtractorMediaPeriod.ExtractingLoadable>, Loader.ReleaseCallback, Loader.Callback<ExtractorMediaPeriod.ExtractingLoadable>, Loader.ReleaseCallback,
UpstreamFormatChangedListener { UpstreamFormatChangedListener {
/**
* Listener for information about the period.
*/
interface Listener {
/**
* Called when the duration or ability to seek within the period changes.
*
* @param durationUs The duration of the period, or {@link C#TIME_UNSET}.
* @param isSeekable Whether the period is seekable.
*/
void onSourceInfoRefreshed(long durationUs, boolean isSeekable);
}
/** /**
* When the source's duration is unknown, it is calculated by adding this value to the largest * When the source's duration is unknown, it is calculated by adding this value to the largest
* sample timestamp seen when buffering completes. * sample timestamp seen when buffering completes.
@ -61,7 +76,7 @@ import java.util.Arrays;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final Handler eventHandler; private final Handler eventHandler;
private final ExtractorMediaSource.EventListener eventListener; private final ExtractorMediaSource.EventListener eventListener;
private final MediaSource.Listener sourceListener; private final Listener listener;
private final Allocator allocator; private final Allocator allocator;
private final String customCacheKey; private final String customCacheKey;
private final long continueLoadingCheckIntervalBytes; private final long continueLoadingCheckIntervalBytes;
@ -103,7 +118,7 @@ import java.util.Arrays;
* @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. * @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 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. * @param eventListener A listener of events. May be null if delivery of events is not required.
* @param sourceListener A listener to notify when the timeline has been loaded. * @param listener A listener to notify when information about the period changes.
* @param allocator An {@link Allocator} from which to obtain media buffer allocations. * @param allocator An {@link Allocator} from which to obtain media buffer allocations.
* @param customCacheKey A custom key that uniquely identifies the original stream. Used for cache * @param customCacheKey A custom key that uniquely identifies the original stream. Used for cache
* indexing. May be null. * indexing. May be null.
@ -112,14 +127,14 @@ import java.util.Arrays;
*/ */
public ExtractorMediaPeriod(Uri uri, DataSource dataSource, Extractor[] extractors, public ExtractorMediaPeriod(Uri uri, DataSource dataSource, Extractor[] extractors,
int minLoadableRetryCount, Handler eventHandler, int minLoadableRetryCount, Handler eventHandler,
ExtractorMediaSource.EventListener eventListener, MediaSource.Listener sourceListener, ExtractorMediaSource.EventListener eventListener, Listener listener,
Allocator allocator, String customCacheKey, int continueLoadingCheckIntervalBytes) { Allocator allocator, String customCacheKey, int continueLoadingCheckIntervalBytes) {
this.uri = uri; this.uri = uri;
this.dataSource = dataSource; this.dataSource = dataSource;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.eventHandler = eventHandler; this.eventHandler = eventHandler;
this.eventListener = eventListener; this.eventListener = eventListener;
this.sourceListener = sourceListener; this.listener = listener;
this.allocator = allocator; this.allocator = allocator;
this.customCacheKey = customCacheKey; this.customCacheKey = customCacheKey;
this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes;
@ -376,8 +391,7 @@ import java.util.Arrays;
long largestQueuedTimestampUs = getLargestQueuedTimestampUs(); long largestQueuedTimestampUs = getLargestQueuedTimestampUs();
durationUs = largestQueuedTimestampUs == Long.MIN_VALUE ? 0 durationUs = largestQueuedTimestampUs == Long.MIN_VALUE ? 0
: largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US; : largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US;
sourceListener.onSourceInfoRefreshed( listener.onSourceInfoRefreshed(durationUs, seekMap.isSeekable());
new SinglePeriodTimeline(durationUs, seekMap.isSeekable()), null);
} }
callback.onContinueLoadingRequested(this); callback.onContinueLoadingRequested(this);
} }
@ -477,8 +491,7 @@ import java.util.Arrays;
} }
tracks = new TrackGroupArray(trackArray); tracks = new TrackGroupArray(trackArray);
prepared = true; prepared = true;
sourceListener.onSourceInfoRefreshed( listener.onSourceInfoRefreshed(durationUs, seekMap.isSeekable());
new SinglePeriodTimeline(durationUs, seekMap.isSeekable()), null);
callback.onPrepared(this); callback.onPrepared(this);
} }

View File

@ -39,7 +39,7 @@ import java.io.IOException;
* <p> * <p>
* Note that the built-in extractors for AAC, MPEG PS/TS and FLV streams do not support seeking. * Note that the built-in extractors for AAC, MPEG PS/TS and FLV streams do not support seeking.
*/ */
public final class ExtractorMediaSource implements MediaSource, MediaSource.Listener { public final class ExtractorMediaSource implements MediaSource, ExtractorMediaPeriod.Listener {
/** /**
* Listener of {@link ExtractorMediaSource} events. * Listener of {@link ExtractorMediaSource} events.
@ -89,8 +89,8 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List
private final int continueLoadingCheckIntervalBytes; private final int continueLoadingCheckIntervalBytes;
private MediaSource.Listener sourceListener; private MediaSource.Listener sourceListener;
private Timeline timeline; private long timelineDurationUs;
private boolean timelineHasDuration; private boolean timelineIsSeekable;
/** /**
* @param uri The {@link Uri} of the media stream. * @param uri The {@link Uri} of the media stream.
@ -155,8 +155,7 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List
@Override @Override
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) { public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
sourceListener = listener; sourceListener = listener;
timeline = new SinglePeriodTimeline(C.TIME_UNSET, false); notifySourceInfoRefreshed(C.TIME_UNSET, false);
listener.onSourceInfoRefreshed(timeline, null);
} }
@Override @Override
@ -182,19 +181,27 @@ public final class ExtractorMediaSource implements MediaSource, MediaSource.List
sourceListener = null; sourceListener = null;
} }
// MediaSource.Listener implementation. // ExtractorMediaPeriod.Listener implementation.
@Override @Override
public void onSourceInfoRefreshed(Timeline newTimeline, Object manifest) { public void onSourceInfoRefreshed(long durationUs, boolean isSeekable) {
long newTimelineDurationUs = newTimeline.getPeriod(0, period).getDurationUs(); // If we already have the duration from a previous source info refresh, use it.
boolean newTimelineHasDuration = newTimelineDurationUs != C.TIME_UNSET; durationUs = durationUs == C.TIME_UNSET ? timelineDurationUs : durationUs;
if (timelineHasDuration && !newTimelineHasDuration) { if (timelineDurationUs == durationUs && timelineIsSeekable == isSeekable
// Suppress source info changes that would make the duration unknown when it is already known. || (timelineDurationUs != C.TIME_UNSET && durationUs == C.TIME_UNSET)) {
// Suppress no-op source info changes.
return; return;
} }
timeline = newTimeline; notifySourceInfoRefreshed(durationUs, isSeekable);
timelineHasDuration = newTimelineHasDuration; }
sourceListener.onSourceInfoRefreshed(timeline, null);
// Internal methods.
private void notifySourceInfoRefreshed(long durationUs, boolean isSeekable) {
timelineDurationUs = durationUs;
timelineIsSeekable = isSeekable;
sourceListener.onSourceInfoRefreshed(
new SinglePeriodTimeline(timelineDurationUs, timelineIsSeekable), null);
} }
} }