From 108f4f14f3b7788472153925cb67495df3c43c9a Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 19 Jul 2017 07:43:43 -0700 Subject: [PATCH] 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 --- .../source/ExtractorMediaPeriod.java | 29 ++++++++++----- .../source/ExtractorMediaSource.java | 35 +++++++++++-------- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java index 45c7eadb21..e7273f834b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java @@ -50,6 +50,21 @@ import java.util.Arrays; Loader.Callback, Loader.ReleaseCallback, 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 * sample timestamp seen when buffering completes. @@ -61,7 +76,7 @@ import java.util.Arrays; private final int minLoadableRetryCount; private final Handler eventHandler; private final ExtractorMediaSource.EventListener eventListener; - private final MediaSource.Listener sourceListener; + private final Listener listener; private final Allocator allocator; private final String customCacheKey; 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 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 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 customCacheKey A custom key that uniquely identifies the original stream. Used for cache * indexing. May be null. @@ -112,14 +127,14 @@ import java.util.Arrays; */ public ExtractorMediaPeriod(Uri uri, DataSource dataSource, Extractor[] extractors, int minLoadableRetryCount, Handler eventHandler, - ExtractorMediaSource.EventListener eventListener, MediaSource.Listener sourceListener, + ExtractorMediaSource.EventListener eventListener, Listener listener, Allocator allocator, String customCacheKey, int continueLoadingCheckIntervalBytes) { this.uri = uri; this.dataSource = dataSource; this.minLoadableRetryCount = minLoadableRetryCount; this.eventHandler = eventHandler; this.eventListener = eventListener; - this.sourceListener = sourceListener; + this.listener = listener; this.allocator = allocator; this.customCacheKey = customCacheKey; this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; @@ -376,8 +391,7 @@ import java.util.Arrays; long largestQueuedTimestampUs = getLargestQueuedTimestampUs(); durationUs = largestQueuedTimestampUs == Long.MIN_VALUE ? 0 : largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US; - sourceListener.onSourceInfoRefreshed( - new SinglePeriodTimeline(durationUs, seekMap.isSeekable()), null); + listener.onSourceInfoRefreshed(durationUs, seekMap.isSeekable()); } callback.onContinueLoadingRequested(this); } @@ -477,8 +491,7 @@ import java.util.Arrays; } tracks = new TrackGroupArray(trackArray); prepared = true; - sourceListener.onSourceInfoRefreshed( - new SinglePeriodTimeline(durationUs, seekMap.isSeekable()), null); + listener.onSourceInfoRefreshed(durationUs, seekMap.isSeekable()); callback.onPrepared(this); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java index 1749e6abf2..51e9757165 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaSource.java @@ -39,7 +39,7 @@ import java.io.IOException; *

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